Eugen Paraschiv 2014-05-24
1. 概述
本教程将重点介绍如何在 Spring 项目中引入 Spring Data JPA,并完整配置持久层。
2. Spring Data 自动生成的 DAO —— 无需再编写 DAO 实现类
正如我们之前文章中讨论的那样,DAO 层通常包含大量样板代码,这些代码完全可以而且应该被简化。这种简化带来的好处很多:减少了需要定义和维护的工件数量、统一了数据访问模式以及配置的一致性。
Spring Data 将这种简化更进一步,使得我们可以完全省略 DAO 的实现类。现在,我们唯一需要显式定义的工件就是 DAO 接口。
要开始在 JPA 中使用 Spring Data 编程模型,DAO 接口需要继承 JPA 特定的 Repository 接口 JpaRepository。这样 Spring Data 就能识别该接口,并自动为其创建实现。
通过继承该接口,我们将获得标准 DAO 中最常用的 CRUD 方法。
3. 自定义访问方法与查询
如前所述,通过实现某个 Repository 接口,DAO 已经具备了一些基本的 CRUD 方法(及对应的查询)。
若要定义更具体的访问方法,Spring JPA 提供了多种选项:
- 直接在接口中定义新方法
- 使用
@Query注解提供具体的 JPQL 查询 - 使用 Spring Data 中更高级的 Specification 和 Querydsl 支持
- 通过 JPA 命名查询(Named Queries)定义自定义查询
第三种选项(Specifications 和 Querydsl)类似于 JPA Criteria API,但提供了更灵活、更便捷的 API。这使得整个操作更具可读性和可重用性。当我们面对大量固定查询时,这种 API 的优势尤为明显——我们可以用更少、更可复用的代码块来表达这些查询。
最后一种选项(命名查询)的缺点在于:要么需要使用 XML 配置,要么会把查询逻辑直接写入领域类中,造成污染。
3.1. 自动生成的自定义查询
当 Spring Data 创建新的 Repository 实现时,它会分析接口中定义的所有方法,并尝试根据方法名自动生成查询语句。虽然这种方式有一定限制,但它是一种非常强大且优雅的方式,只需极少的工作量即可定义新的自定义访问方法。
来看一个例子。如果实体类中有一个 name 字段(并遵循 Java Bean 规范提供了 getName() 和 setName() 方法),我们在 DAO 接口中定义 findByName 方法,系统就会自动生成正确的查询:
public interface IFooDAO extends JpaRepository<Foo, Long> {
Foo findByName(String name);
}
这是一个相对简单的示例。实际上,查询生成机制支持更丰富的关键字组合。
如果解析器无法将方法中的属性名匹配到领域对象的字段,将会抛出如下异常:
java.lang.IllegalArgumentException: No property nam found for type class com.baeldung.jpa.simple.model.Foo
3.2. 手动编写的自定义查询
下面我们看一个通过 @Query 注解定义的自定义查询:
@Query("SELECT f FROM Foo f WHERE LOWER(f.name) = LOWER(:name)")
Foo retrieveByName(@Param("name") String name);
如果需要对查询创建进行更细粒度的控制(例如使用命名参数或修改已有查询),可以参考官方文档获取更多信息。
4. 事务配置
由于我们并不直接操作 Spring 管理的 DAO 实现类,因此其实现是隐藏的。但实际上,它的实现非常简单——即 SimpleJpaRepository,该类通过注解定义了事务语义。
具体来说,它在类级别使用了一个只读的 @Transactional 注解,并在非只读方法上进行覆盖。其余的事务语义采用默认配置,当然也可以按需为每个方法手动覆盖。
4.1. 异常转换依然有效
现在的问题是:既然 Spring Data JPA 不再依赖旧的 ORM 模板类(如 JpaTemplate、HibernateTemplate),而这些模板类自 Spring 5 起已被移除,我们是否还能将 JPA 异常自动转换为 Spring 的 DataAccessException 异常体系?
答案当然是肯定的。只要在 DAO 上使用 @Repository 注解,就能启用异常转换功能。该注解会触发 Spring 的 bean 后处理器,为所有带有 @Repository 注解的 bean 添加 PersistenceExceptionTranslator 实例,从而像以前一样提供异常转换。
我们可以通过集成测试来验证异常转换是否生效:
@Test(expected = DataIntegrityViolationException.class)
public void whenInvalidEntityIsCreated_thenDataException() {
service.create(new Foo());
}
需要注意的是,异常转换是通过代理实现的。为了让 Spring 能够为 DAO 类创建代理,这些类不能声明为 final。
5. Spring Data JPA Repository 配置
要启用 Spring JPA Repository 支持,我们可以使用 @EnableJpaRepositories 注解,并指定包含 DAO 接口的包路径:
@EnableJpaRepositories(basePackages = "com.baeldung.jpa.simple.repository")
public class PersistenceConfig {
...
}
也可以使用 XML 配置实现相同效果:
<jpa:repositories base-package="com.baeldung.jpa.simple.repository" />
6. Java 或 XML 配置
我们已在之前的文章中详细讨论过如何在 Spring 中配置 JPA。Spring Data 也充分利用了 Spring 对 JPA @PersistenceContext 注解的支持。它利用该注解将 EntityManager 注入到负责创建实际 DAO 实现的 Spring 工厂 bean(即 JpaRepositoryFactoryBean)中。
除了上述配置外,如果我们使用 XML 配置,还需引入 Spring Data 的 XML 配置文件:
@Configuration
@EnableTransactionManagement
@ImportResource("classpath*:*springDataConfig.xml")
public class PersistenceJPAConfig {
...
}
7. Maven 依赖
除了之前文章中提到的 JPA 相关 Maven 配置外,我们还需要添加 spring-data-jpa 依赖:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>3.5.7</version>
</dependency>
8. 使用 Spring Boot
我们也可以使用 Spring Boot 的 Starter Data JPA 依赖,它会自动为我们配置 DataSource。
只需确保所选数据库驱动已加入 classpath。在本例中,我们添加了 H2 内存数据库:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
仅凭这些依赖,我们的应用即可启动运行,并可用于其他数据库操作。
原本在标准 Spring 应用中所需的显式配置,现在都由 Spring Boot 的自动配置机制完成。
当然,我们也可以通过添加自定义显式配置来覆盖自动配置。
Spring Boot 提供了一种简便方式:通过 application.properties 文件中的属性进行配置。例如,修改连接 URL 和凭证:
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa
9. Spring Data JPA 的实用工具
Spring Data JPA 在主流 Java IDE 中均得到良好支持。下面分别介绍 Eclipse 和 IntelliJ IDEA 中的实用工具。
Eclipse
如果你使用 Eclipse,可以安装 Dali Java Persistence Tools 插件。它提供以下功能:
- JPA 实体的 ER 图
- 用于初始化数据库结构的 DDL 生成
- 基础的反向工程能力
此外,你也可以使用 Eclipse Spring Tool Suite (STS),它能帮助验证 Spring Data JPA Repository 中的查询方法名是否合法。
IntelliJ IDEA
IntelliJ IDEA 提供两种选择:
Ultimate 版本:支持 ER 图、JPA 控制台(用于测试 JPQL 语句)以及有价值的代码检查功能。
注意:这些功能在 Community(社区版)中不可用。
JPA Buddy 插件:无论你使用 Community 还是 Ultimate 版本,都可以安装此插件以提升开发效率。它提供诸多功能,包括:
- JPA 实体生成
- Spring Data JPA Repository 生成
- DTO 生成
- 初始化 DDL 脚本生成
- Flyway 版本化迁移脚本
- Liquibase changelog 生成
- 高级反向工程工具
10. 结论
本文介绍了如何在 Spring 5、JPA 2 和 Spring Data JPA(Spring Data 项目的一部分)环境下,使用 Java 或 XML 配置方式搭建和实现持久层。
我们探讨了定义高级自定义查询的方法、事务语义的处理,以及使用新的 jpa 命名空间进行配置。最终结果是一种新颖而优雅的 Spring 数据访问方式——几乎无需编写任何实际实现代码。