In this lab you will get an introduction into JPA.
What you will learn:
Specific subjects you will gain experience with:
Estimated time to complete: 30 minutes
Quick instructions for this exercise have been embedded within
the lab materials in the form of TODO comments. To display them,
open the Tasks view (Window >> Show View >>
Tasks (not Task
List)).
The table and column names are defined by schema.sql (in src/test/resources/rewards/testdb) and are
shown in this schema diagram:
Recall the Account entity
represents a member account (a diner) in the reward network that
can make contributions to its beneficiaries. In this step,
you’ll review the JPA annotations that map the Account object
graph to the database.
TODO-01) Open Account class in the rewards.internal.account package. As you can
see this class is already mapped with several JPA annotations.
Let’s walk through these annotations one at a time to
understand what each one is doing. Later in this lab, you will need
to annotate a class on your own, so this is a good chance to look
at a good object-to-relational mapping example.@Entity annotation on
this class. This specifies that this class will be loaded from the
database using the JPA implementation. As a default JPA treats the
class name as the table name (in this case it would be ACCOUNT). Note the @Table annotation is used to override the
default name - in our database schema above, the accounts table is
T_ACCOUNT.Every entity needs a primary key so it can be managed by JPA.
Every table in this lab uses auto-generated numeric keys. A long
integer entityId field has been added
to each classes to be mapped. As you can see, it is annotated with
@Id, this means that the database will
treat the matching column as the table’s primary key.
![]() |
Note |
|---|---|
|
Although you don’t see it here, whenever we create new
database rows we need to tell JPA how the primary key values are
auto-generated. Normally the |
@Column.Account can have many
beneficiaries, its beneficiaries
property is a collection. In the database, this relationship is
modeled by a one-to-many relationship with a T_ACCOUNT_BENEFICIARY table. To describe
this relationship to JPA, the @OneToMany annotation is used. The
foreign-key column in the T_ACCOUNT_BENEFICIARY table is ACCOUNT_ID, and we tell JPA about this
foreign key column using @JoinColumn.Of course, there is a lot to JPA that we are glossing over - such as all of the options that we can express on the various annotations. JPA is a deep subject and we are only covering the basics here. Once you feel you understand how this class is mapped, move on to the next step.
(TODO-02) Open the Beneficiary class and observe its JPA
annotations. Again, we have annotated most if this class for you - except entityId.
@Entity, @Table, @Id,
and @Column annotations are used in
exactly the same way as in the Account class.entityId field - refer back to
what you have already seen for Account
(TODO-2b).Note the beneficiary savings and
allocationPercentage fields are of the
custom types MonetaryAmount and Percentage respectively. Out of the box, JPA
does not know how to map these custom types to database columns. It
is possible to define custom getters and setters (used only by JPA)
to do the conversion. However there is a simpler way - using @AttributeOverride
Both MonetaryAmount and Percentage have a single data-member called
value. This needs to be mapped to the correct column in the
Beneficiaries table. This involves using the @AttributeOverride annotation. We must map
the field name value to the column for
savings and allocationPercentage respectively.
Now it’s your turn! Recall the Restaurant entity represents a merchant in
the reward network that calculates how much benefit to reward to an
account for dining. In this step, you’ll configure the JPA
mapping annotations that map the Restaurant object graph to the T_RESTAURANT table.
![]() |
Tip |
|---|---|
|
The column names in |
Restaurant class in the
rewards.internal.restaurant package
(TODO-03).Account module, we need
to mark this class as an entity, define its table and define its
entityId field as the primary key
(don’t forget to use a Column
annotation to specify the target column name in the database for
entityId).Complete the mapping for the remaining Restaurant fields: number, name
and benefitPercentage. Like the Beneficiary mapping, the percentage is a
custom type and needs mapping differently.
![]() |
Tip |
|---|---|
|
You will need to use the |
Once you have completed the Restaurant mapping, all of the domain
classes are completely annotated for JPA.
In the next steps, we will implement some code to query the database using JPA.
Now that we have our ORM mapping data defined, we can write some code that uses a JPA EntityManager to query the persistent objects in various ways.
Open JpaAccountRepository (TODO-04). You are going to implement the
method to look up an Account using a credit card number string.
Open JpaRestaurantRepository (TODO-05). This class uses a JPA query to
lookup a Restaurant using a merchant number string. We have
implemented it for you, since it is very similar to the JpaAccountRepository you just wrote.
TODO-06) Open RewardsConfig.java and add an annotation to
the class that will scan for the JPA repository classes. Within
this annotation, you should specify the base package under which
all repositories can be found.(TODO-07) Open SystemTestConfig.java and review the @Bean method for the Entity Manager Factory
Bean. We have configured the JpaVendorAdapter and a Properties object for you. The rest is up to
you:
LocalContainerEntityManagerFactoryBean.dataSource and jpaVendorAdapter properties appropriately.
The jpaVendorAdapter tells Spring
which JPA implementation will be used to create an
EntityManagerFactory instance. We are using Hibernate, so we create
a HibernateJpaVendorAdapter.You can set additional JPA implementation specific configuration
properties by setting the jpaProperties property. We have setup hibernate.format_sql=true for you (to make
any generated SQL readable).
Finally (TODO-08) define a PlatformTransactionManager bean so the
Reward Network can drive transactions using JPA Entity
Managers.
transactionManager.JpaTransactionManager
implementation.Note that this class requires an entityManagerFactory, so we will need to
inject one. Fortunately, Spring makes this very easy - simply
define the bean method with a parameter of type EntityManagerFactory.
![]() |
Note |
|---|---|
|
When Spring calls your transaction manager |
That was a lot of Spring configuration - in the next lab we will get Spring Boot to do it all for us.
(TODO-09) Now we should be able to
run a test and verify all your work. Open the RewardNetworkTests class, remove the @Disabled annotation on the
testRewardForDining method, and run the test. It should pass.
If you have a successfully running test, congratulations!
What You Have Achieved. You have annotated domain objects with JPA annotations, implemented repositories, defined beans for the EntityManagerFactory.