分类
Spring Data

Spring Data JPA 简介

Introduction to Spring Data JPA

1. 概述

本文将围绕Spring项目中的Spring Data JPA展开介绍。如果你尚需要了解如何使用Spring来构造一个基本的应用程序,那么可以先阅读此篇文章。Spring JPA是一种新颖、优雅的数据访问方式,它允许我们在只定义接口及方法名称的前提下,快速的实现对数据库的访问。

2. Spring Data中的DAO

DAO模式:Data Access Object数据访问(存储)对象模式,简单来讲就是使用JAVA语言来快速、简单的操作数据库。

该模式一般由以下部分组成:

DAO接口:将对数据库的操作定义为抽象方法,比如定义save方来新增数据。

DAO实现类:负责DAO接口中抽象方法的具体实现。往往会根据数据库类型而给出不同的实现。

实体类:用于与数据库中的数据表进行映射。DAO可以将实体对象操作到数据表中,也可以将查询出的数据绑定到实体上。

基础类:为一些样板代码提供一些基础的支持,避免写过多的样板化代码。

https://www.runoob.com/note/27029

使用Spring简化DAO层的样板代码一文中,我们阐述了DAO层通常包含了过多重复的样板化的代码。对样板代码简单可以减少代码体量,降低维护与升级的成本,统一数据访问方式、配置信息等。

Spring Data对样板代码的简化做到了极致,在Spring Data中我们只需要定义相关接口即可,完全不需要对该接口进行实现。

若使定义的DAO接口生效,则需要使其继承JPA中指定的仓库接口 -- JpaRepository。Spring Data将会自动扫描继承了JpaRepository的接口并自动为其创建一个实现。

通过继承JpaRepository的方法,我们可以轻松的获取到一个包含了CRUD操作的标准DAO。

3. 自定义方法以及查询

虽然能过继承JpaRepository已经实现了基本的CRUD操作,但大多数的项目中,仅仅有CRUD操作是远远不够的。

为此,Spring JPA提供了多种自定义数据操作的方法:

  • 直接在接口中定义一个新的方法,并使用支持JPQL的@Query注解来进行标识
  • 使用高级用法:SpecificationQuerydsl
  • 通过JPA命名查询(Named Queries)来自定义查询方法

第二种方法与JPA的标准查询比较相似,不同的是使用这种方法将更加灵活、方便。这将使我们的代码具有更高的可读性,可复用性也会更强。特别是当我们处理一些复杂的查询逻辑时,这种方法的优势将更加突出。

3.1 仅需要定义方法名

当Spring Data扫描到继承了JpaRepository的接口并生成实现时,Spring Data将扫描接口定义的方法并尝试依据方法名将其自动转换为特定的查询语句。尽管说这种依据方法名来自动生成查询语句的方法有一定的局限性,但这种方法使用起来真的是太强大、太方便、太优雅了。

假设当前有学生(student)实体,我们此时想以学生姓名做为关键字进行查询,则仅仅需要在定义的DAO接口中加入以下方法:

/**
 * 操作学生表的DAO
 *
 * @author panjie
 */
public interface StudentDAO extends JpaRepository<Student, Long> {
    /**
     * 通过姓名查找某个学生
     *
     * @param name 学生姓名
     * @return
     */
    Student findByName(String name);
}

没错,仅仅需要定义一个方法,Spring Data将自动按此方法名称转换为:根据关键字来查询某个学生。这种查询方法支持很多种关键字,如果在不需要对逻辑进行处理时,不失为最佳的一种方法。更多的关键字请参考官方文档

当然了,在使用上述方法时,需要保证实体中的字段信息与方法中给出的信息一致,以避免发生异常。比如findByUsername方法的成功执行依赖于学生实体(student)中存在username字段,而此时学生实体并不存在userName字段,则解析器将抛出如下异常:

club.codedemo.thepersistencelayerwithspringdatajpa.StudentDAO.findByUsername(java.lang.String)! No property username found for type Student!

3.2 自定义查询语句

可以在方法上添加@Query注解以自定义查询语句:

    /**
     * 不区分大小写的根据name查询某个学生
     * 比如通过Zhangsan可以查询出学生名为zhangsan、zhangSan等的学生
     *
     * @param name 学生姓名
     * @return
     */
    @Query("SELECT s FROM Student s WHERE LOWER(s.name) = LOWER(:name)")
    Student retrieveByName(@Param("name") String name);

欲了解更多关于自定义查询语句的知识,请参考官方文档

4. 事务配置

Spring自动实现接口的特性决定了我们无法直接获取到整个实现过程,当然也无法得知Spring Data JPA是如何对事务进行配置的了。值得庆幸的事,我们可以通过观察Spring Data JPA中的org.springframework.data.jpa.repository.support.SimpleJpaRepository类了解其事务配置。

Spring使用了@Transaction(readOnly = true)对该类进行注解,表示该类中的方法默认采用的都是只读(read-only)模式。接着又在个别的非查询模式的方法上加入了@Transaction,从而覆盖了在类上标注的只读模式,近而达到了:如果该类中的某个方法上没有使用@Transaction注解,则标识在该类上的@Transaction(readOnly = true)起作用;如果某个方法使用了@Transaction注解,则忽略类上的@Transaction(readOnly = true)注解。

4.1 异常转换依然有效

现在可能你有一个疑问:既然Srping Data JPA并不依赖于已经在Spring5中移除的历史的模板引擎JpaTemplate与HibernateTemplate,那么我们是否仍要将JPA异常转换为Spring的DataAccessException呢?

答案是肯定的,我们仍然可以在DAO层使用@Repository注解来开启异常转换功能。@Repository注解将自动获取bean中的PersistenceExceptionTranslator并将其转换为我们熟知的DataAccessException

比如执行以下代码最终将获取DataIntegrityViolationException(该异常是DataAccessException的子类):

    /**
     * 由于Student实体中要求name字段不能为null
     * 所以保存一个name字段为null的默认学生实体时,将会发生DataIntegrityViolationException异常
     */
    @Test
    void givenStudentHaveNoName() {
        Student student = new Student();
        Assertions.assertThrows(DataIntegrityViolationException.class,
                () -> this.studentDAO.save(student));
    }

需要时刻提醒自己的是:和很多注解一样,异常转换是通过代码模式完成的。所以相关的方法绝对不能够声明为final

5. Spring Data JPA仓库配置

使用@EnableJpaRepositories来启用Spring JPA的仓库支持(自动根据接口创建DAO实现),同时在该注解中需要同时指定扫描的基础包:

/**
 * 测试EnableJpaRepositories注解时,请 注释/启用 11,12,13行后
 * 分别查看单元测试EnableJpaRepositoriesTest中获取bean的效果
 */
@SpringBootApplication
@EnableJpaRepositories(
		basePackages = "club.codedemo.repository"
)
public class ThePersistenceLayerWithSpringDataJpaApplication {

使用XML的话,配置如下:

<jpa:repositories base-package="com.baeldung.spring.data.persistence.repository" />

6. JAVA或XML配置

在认识Spring JPA一文中已经对如何在Spring中配置JPA进行了深入了讨论。除了在前文中讨论的内容以外,如果说我们使用XML进行配置,则需要在@ImportResource中指定XML的位置:

@Configuration
@EnableTransactionManagement
@ImportResource("classpath*:*springDataConfig.xml")
public class PersistenceJPAConfig {
    ...
}

7. MAVEN依赖

就像前文中提及的一样,使用JPA还需要加入spring-data-jpa依赖:

<dependency>
   <groupId>org.springframework.data</groupId>
   <artifactId>spring-data-jpa</artifactId>
   <version>2.2.7.RELEASE</version>
</dependency>

8. 使用Spring Boot

我们还可以使用Spring Boot Starter Data JPA依赖,其将会自动的为项目配置数据源。

当然了,自动配置数据源的前提是需要让其能够检测到当前项目使用的数据源,如果在项目中并没有提供任何数据源,则将发生一个异常。比如我们在项目中添加H2内容数据库:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
   <version>2.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.200</version>
</dependency>

则项目在启动时,将自动加载H2数据库做为数据源。

在未进行任何配置的情况下,Spring Boot在启动时,将会按标准的默认配置进行加载。这些默认的配置项可以轻松的通过application.properties文件进行更改:

spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa

比如我们通过以代码来修改数据源的地址以及数据库的认证信息。

9. 总结

本文对Spring 5、JPA2、Sptring Data JPA中数据持久化层的配置及实现进行介绍。在介绍的过程中给出了基于xml以及基于java的配置示例。

本文还讨论了自定义高级查询的几种方法、事务机制、异常转换以及如何扫描一个项目外的jpa命名空间。

总之Spring JPA是一种新颖、优雅的数据访问方式,它允许我们在只定义接口及方法名称的前提下,快速的实现对数据库的访问。