10 Orm Integration How to Use Spring Data Jpa to Access Relational Databases

10 ORM Integration - How to Use Spring Data JPA to Access Relational Databases #

In the previous courses, we have discussed in detail how to implement data access using the JdbcTemplate template tool provided by Spring. Compared to the native API provided by JDBC, JdbcTemplate provides a layer of abstraction that greatly simplifies the data manipulation process. In Lesson 09, we introduced the Spring Data framework, which can be seen as a higher-level abstraction on top of JdbcTemplate.

Today, we will introduce how to integrate ORM framework to access relational databases using Spring Data JPA, which is a component of Spring Data.

Introducing Spring Data JPA #

If you want to use Spring Data JPA in your application, you first need to add the spring-boot-starter-data-jpa dependency to your pom file, as shown in the code below:

    
<dependency>

     <groupId>org.springframework.boot</groupId>

     <artifactId>spring-boot-starter-data-jpa</artifactId>

</dependency>

Before we explore how to use this component, it’s necessary to have a basic understanding of the JPA specification.

JPA, short for Java Persistence API, is a Java application interface specification that serves as a bridge between object-oriented domain models and relational database systems. It is an ORM (Object Relational Mapping) technology.

The JPA specification defines some commonly used concepts and conventions, which are mainly included in the javax.persistence package. Examples include entity definition, entity identification definition, entity associations, and the JPQL (Java Persistence Query Language) we introduced in Lesson 09. We will explain these definitions and their usage in detail later.

Similar to the JDBC specification, there are also a variety of tools and frameworks available for implementing the JPA specification. The most representative ones include the well-established Hibernate and Spring Data JPA, which we will introduce today.

To demonstrate the entire development process using Spring Data JPA, we will design and implement a set of domain objects and repositories specifically for the SpringCSS case. Let’s take a look.

Entity Class Annotations #

We know that there are two main domain objects in the order-service: Order and Goods. In order to differentiate them from the domain objects introduced in previous lessons, we will create two new domain objects named JpaOrder and JpaGoods, which are the entity classes defined in the JPA specification.

Let’s start with the relatively simple JpaGoods. Here, we list the relevant classes from the JPA specification together, and the definition of JpaGoods is as follows:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="goods")
public class JpaGoods {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;    
    private String goodsCode;
    private String goodsName;
    private Float price;    
    
    // ...

}

JpaGoods uses several annotations from the JPA specification for entity definition: the most important one is the @Entity annotation, which specifies that this class is an entity; the @Table annotation is used to specify the table name; the @Id annotation is used to identify the primary key; and the @GeneratedValue annotation is used to specify that the primary key is auto-generated. These annotations are straightforward and can be used directly on the entity class.

Next, let’s take a look at the more complex JpaOrder, defined as follows:

@Entity
@Table(name="`order`")
public class JpaOrder implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String orderNumber;
    private String deliveryAddress;

    @ManyToMany(targetEntity=JpaGoods.class)
    @JoinTable(name = "order_goods", joinColumns = @JoinColumn(name = "order_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "goods_id", referencedColumnName = "id"))
    private List<JpaGoods> goods = new ArrayList<>();

    // ...

}

Here, in addition to the common annotations, we introduced the @ManyToMany annotation, which represents the association between the data in the order table and goods table.

In the JPA specification, there are four types of mapping relationships provided: one-to-one, one-to-many, many-to-one, and many-to-many. These relationships are used to handle one-to-one, one-to-many, many-to-one, and many-to-many association scenarios, respectively.

For the order-service business scenario, we designed an intermediate table order_goods to store the primary key relationship between the order and goods tables. We used the @ManyToMany annotation to define the many-to-many association, and used the @JoinTable annotation to specify the order_goods intermediate table, and used the joinColumns and inverseJoinColumns annotations to specify the field names in the intermediate table and the foreign key names referencing the two main tables, respectively.

Defining the Repository #

After defining the entity objects, we can now provide a repository interface. This step is very simple. The definition of OrderJpaRepository is as follows:

@Repository("orderJpaRepository")
public interface OrderJpaRepository extends JpaRepository<JpaOrder, Long> {
}

From the above code, we can see that OrderJpaRepository is an empty interface that extends the JpaRepository interface. Based on the introduction in Lesson 09, we know that OrderJpaRepository actually has the basic CRUD functionality to access the database.

Accessing the Database Using Spring Data JPA #

With the JpaOrder and JpaGoods entity classes defined above, as well as the OrderJpaRepository interface, we can now perform many operations.

For example, if we want to get an Order object by Id, we can first build a JpaOrderService and autowire the OrderJpaRepository interface, as shown in the following code:

@Service
public class JpaOrderService {

    @Autowired
    private OrderJpaRepository orderJpaRepository;

    public JpaOrder getOrderById(Long orderId) {
        return orderJpaRepository.getOne(orderId);
    }
}

Then, by building a Controller class to embed the above method, we can query the JpaOrder object with Id 1 through an HTTP request. The result we get is as follows:

{
    "id": 1,
    "orderNumber": "Order10001",
    "deliveryAddress": "test_address1",
    "goods": [
        {
            "id": 1,
            "goodsCode": "GoodsCode1",
            "goodsName": "GoodsName1",
            "price": 100.0
        },
        {
            "id": 2,
            "goodsCode": "GoodsCode2",
            "goodsName": "GoodsName2",

The rest of the content is truncated. “price”: 200.0

        }

    ]

}

Note that here we not only get the basic order data from the order table, but also get the detailed product data from the goods table. How is this achieved? It’s because we added the @ManyToMany annotation to the JpaOrder object, which automatically retrieves the product primary key information from the order_goods table and the product detailed information from the goods table.

After understanding the process of accessing relational databases using Spring Data JPA and comparing it with the implementation process of retrieving this data through JdbcTemplate in the “Data Access: How to Access Relational Databases Using JdbcTemplate?” lecture, we find that using Spring Data JPA is simpler.

In the process of implementing diversified queries, we can not only use the various CRUD methods integrated in the JpaRepository, but also use the @Query annotation and method name-derived queries introduced in Lecture 09. Today, we will also introduce the Query By Example (QBE) and Specification mechanisms to enrich the ways of diversified queries.

Using the @Query Annotation #

Here is an example of using the @Query annotation to implement a query:

@Repository("orderJpaRepository")

public interface OrderJpaRepository extends JpaRepository<JpaOrder, Long>

{

 

    @Query("select o from JpaOrder o where o.orderNumber = ?1")

    JpaOrder getOrderByOrderNumberWithQuery(String orderNumber);

}

Here, we used JPQL to query order information based on the OrderNumber. The syntax of JPQL is very similar to SQL, and we discussed JPQL in Lecture 09, so we won’t go into it again here. You can review it there.

Speaking of the @Query annotation, JPA also provides a @NamedQuery annotation to name the statement in the @Query annotation. The usage of the @NamedQuery annotation is shown in the following code:

@Entity

@Table(name = "`order`")

@NamedQueries({ @NamedQuery(name = "getOrderByOrderNumberWithQuery", query = "select o from JpaOrder o where o.orderNumber = ?1") })

public class JpaOrder implements Serializable {

In the above example, we added a @NamedQueries annotation to the entity class JpaOrder, which can be used to integrate a batch of @NamedQuery annotations together. At the same time, we used the @NamedQuery annotation to define a “getOrderByOrderNumberWithQuery” query and specified the corresponding JPQL statement.

If you want to use this named query, just define a method in OrderJpaRepository with the same name.

Using Method Name-Derived Queries #

Using method name-derived queries is the most convenient way of customizing queries, and the only thing developers need to do in this process is to define a method in the JpaRepository interface that conforms to the query semantics.

For example, if we want to query order information based on the OrderNumber, we can provide the following interface definition:

@Repository("orderJpaRepository")

public interface OrderJpaRepository extends JpaRepository<JpaOrder, Long>

{

    JpaOrder getOrderByOrderNumber(String orderNumber);

}

With the getOrderByOrderNumber method, we can automatically retrieve the detailed order information based on the OrderNumber.

Using the Query By Example Mechanism #

Next, we will introduce another powerful query mechanism, the Query By Example (QBE) mechanism.

For the JpaOrder object, suppose we want to query based on the OrderNumber and one or more conditions in the DeliveryAddress. After building the query method using the method name-derived query approach, we get the following method definition:

List<JpaOrder> findByOrderNumberAndDeliveryAddress (String 

	orderNumber, String deliveryAddress);

If we use a lot of fields in the query criteria, the method name above can be very long, and we also need to set a batch of parameters. This kind of query method definition obviously has flaws.

Because no matter how many query criteria there are, we need to fill in all the parameters, even if some of the parameters are not used. Moreover, if we need to add a new query criteria in the future, this method must be adjusted, which has a design flaw in terms of extensibility. To solve these problems, we can introduce the Query By Example mechanism.

Query By Example, or QBE, is a user-friendly query technique. It allows us to dynamically create queries without the need to write query methods that contain field names, which means that QBE does not requires using a specific database query language to write query statements.

In terms of composition, Query By Example includes the Probe, ExampleMatcher, and Example components. Among them, the Probe contains instances of the corresponding fields, ExampleMatcher carries detailed information on how to match specific fields, which is equivalent to matching conditions, and Example is composed of Probe and ExampleMatcher, which is used to build specific query operations.

Now, let’s refactor the implementation process of querying orders based on the OrderNumber using the Query By Example mechanism.

First, we need to inherit the QueryByExampleExecutor interface in the definition of the OrderJpaRepository interface, as shown in the following code:

@Repository("orderJpaRepository")
public interface OrderJpaRepository extends JpaRepository<JpaOrder, Long>, QueryByExampleExecutor<JpaOrder> {

然后我们在 JpaOrderService 中实现如下代码所示的 getOrderByOrderNumberByExample 方法

public JpaOrder getOrderByOrderNumberByExample(String orderNumber) {

    JpaOrder order = new JpaOrder();

    order.setOrderNumber(orderNumber);

    

    ExampleMatcher matcher = ExampleMatcher.matching().withIgnoreCase()

        .withMatcher("orderNumber", GenericPropertyMatchers.exact()).withIncludeNullValues();

    

    Example<JpaOrder> example = Example.of(order, matcher);

    

    return orderJpaRepository.findOne(example).orElse(new JpaOrder());

}

上述代码中我们首先构建了一个 ExampleMatcher 对象用于初始化匹配规则然后通过传入一个 JpaOrder 对象实例和 ExampleMatcher 实例构建了一个 Example 对象最后通过 QueryByExampleExecutor 接口中的 findOne() 方法实现了 QueryByExample 机制

#### 使用 Specification 机制

本节课中最后我们想介绍的查询机制是 Specification 机制

先考虑这样一种场景比如我们需要查询某个实体但是给定的查询条件不固定此时该怎么办这时我们通过动态构建相应的查询语句即可而在 Spring Data JPA 中可以通过 JpaSpecificationExecutor 接口实现这类查询相比使用 JPQL 而言使用 Specification 机制的优势是类型安全

继承了 JpaSpecificationExecutor 的 OrderJpaRepository 定义如下代码所示

@Repository("orderJpaRepository")

public interface OrderJpaRepository extends JpaRepository<JpaOrder, Long>,     JpaSpecificationExecutor<JpaOrder>{

对于 JpaSpecificationExecutor 接口而言它背后使用的就是 Specification 接口且 Specification 接口核心方法就一个我们可以简单地理解该接口的作用就是构建查询条件如下代码所示

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);

其中 Root 对象代表所查询的根对象我们可以通过 Root 获取实体的属性CriteriaQuery 代表一个顶层查询对象用来实现自定义查询而 CriteriaBuilder 用来构建查询条件

基于 Specification 机制我们同样对根据 OrderNumber 查询订单的实现过程进行重构重构后的 getOrderByOrderNumberBySpecification 方法如下代码所示

public JpaOrder getOrderByOrderNumberBySpecification(String orderNumber) {

    JpaOrder order = new JpaOrder();

    order.setOrderNumber(orderNumber);

    

    @SuppressWarnings("serial")

    Specification<JpaOrder> spec = new Specification<JpaOrder>() {

        @Override

        public Predicate toPredicate(Root<JpaOrder> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

            Path<Object> orderNumberPath = root.get("orderNumber");

          

            Predicate predicate = cb.equal(orderNumberPath, orderNumber);

            return predicate;

        }

    };

    

    return orderJpaRepository.findOne(spec).orElse(new JpaOrder());     

}

从上面示例中可以看到在 toPredicate 方法中首先我们从 root 对象中获取了orderNumber属性然后通过 cb.equal 方法将该属性与传入的 orderNumber 参数进行了比对从而实现了查询条件的构建过程

### 小结与预告

10 讲中我们主要对通过 Spring Data JPA 进行数据操作的方法和技巧做了一一介绍

在 Spring Boot 中我极力推荐使用 Spring Data JPA 实现对关系型数据库访问因为它不仅具有 ORM 框架的通用功能同时还添加了 QueryByExample 和 Specification 机制等扩展性功能应用上简单而高效