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.