Chapter 12. jpa

12.1. jpa: JPA implementation with Spring

12.2. Introduction

In this lab you will get an introduction into JPA.

What you will learn:

  1. How to configure domain objects with annotations to map these to relational structures

Specific subjects you will gain experience with:

  1. How to inject EntityManager using @PersistenceContext
  2. How to peform JPA query operation

Estimated time to complete: 30 minutes

12.3. Quick Instructions

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:

rewardDining databaseSchema

Figure 12.1. Rewards Database Schema



12.4. Detailed Instructions

12.4.1. Review the Account and Beneficiary mapping annotations

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.

  1. (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.
  2. Note the @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.
  3. 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] 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 @GeneratedValue annotation is used to describe a key-generation strategy. In our case, we will never insert new values into the database so this annotation is not needed, so to keep things simple we’ve omitted it.

  4. By default JPA uses the field name as the column name to map a field into a database table column. In our case, some of the field names don’t match the column names, so we are overriding the mapping by using @Column.
  5. Since an 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.

  1. The @Entity, @Table, @Id, and @Column annotations are used in exactly the same way as in the Account class.
  2. Add annotations for the entityId field - refer back to what you have already seen for Account (TODO-2b).
  3. 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.

12.4.2. Configure the Restaurant mapping

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] Tip

The column names in T_RESTAURANT table do not necessarily match the data-member names in Restaurant. Where they are different, use @Column (refer to schema diagram for help).

  1. Open the Restaurant class in the rewards.internal.restaurant package (TODO-03).
  2. Like the 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).
  3. 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] Tip

    You will need to use the @AttributeOverride annotation again (see Beneficiary class for an example).

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.

12.4.3. Implement JpaAccountRepository class

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.

12.4.4. Review JpaRestaurantRepository class

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.

12.4.5. Spring JPA Configuration

  1. (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.
  2. (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:

    1. The factory bean’s class is LocalContainerEntityManagerFactoryBean.
    2. Set the 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.
    3. 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).

  3. Finally (TODO-08) define a PlatformTransactionManager bean so the Reward Network can drive transactions using JPA Entity Managers.

    1. Call the bean transactionManager.
    2. Use the JpaTransactionManager implementation.
    3. 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] Note

      When Spring calls your transaction manager @Bean method at startup time, it will find the entityManagerFactory by calling the getObject() method on the LocalContainerEntityManagerFactoryBean defined earlier. By letting Spring handle this (instead of calling the method directly), you are allowing Spring to take care of any lifecycle and configuration requirements.

That was a lot of Spring configuration - in the next lab we will get Spring Boot to do it all for us.

12.4.6. Adjust Application Code and Test

(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.