分类
Spring Data

Spring Data JPA 创建、组合自定义仓库

Spring Data Composable Repositories

1. 简介

在对真实世界或操作流程进行建模时,建立领域驱动设计domain-drven design(DDD)风格的数据仓库是个不错的选择。正是基于此,我们在数据访问层选择了Spring Data JPA。

如果您对Spring Data JPA还不太了解,推荐您首先阅读Spring Data JPA简介一文。

在本文中,我们将重点介绍创建自定义数据仓库以及如果组合使用自定义的数据仓库。

2. MAVEN依赖

在Spring自版本5开始支持创建及组合自定义数据仓库,添加Spring Data JPA依赖如下:

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

如果你使用了Spring Boot,应该移除上述依赖中的version版本号(本文代码示例基于Spring Boot)。

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

无疑还要为Spring Data JPA准备一个数据库,比如开源数据库mysql。但在开发测试环境中我们会经常使用内存数据库H2来代替生产环境中的mysql,该数据不需要任何环境的特性极大程度上提长了开发测试效率,当然也特别适用于学习交流中的代码演示。添加依赖如下:

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<scope>runtime</scope>
</dependency>

3. 准备工作

3.1 Hibernate实现

Spring Data JPA默认使用了Hibernate。以致于有些时候,我们误认为为Spring Data JPA就是Hibernate,但本质上来讲Spring Data JPA其实是一种规范,Hibernate则是该规范的一种实现。

在实际的应用中,除Hibernate以外还有其它Spring Data JPA实现可供选择。比如:在Spring中使用EclipseLink

3.2 默认仓库

在多数情况下,我们并不需要写过多的查询语句便可以轻松的实现一般的数据操作功能。在这里,我们仅需要创建一个类似如下的接口:

public interface StudentRepository extends JpaRepository<Student❶, Long❷> {
}
  • ❶实体类
  • ❷主键类型

该接口继承了JpaRepository接口,从而自动实现了:CRUD、分页、排序等基本的数据操作功能。

此外Spring Data JPA还提供了一种根据方法名称自动实现查询功能的机制。比如查询姓名为X的学生列表可以定义如下方法:

public interface StudentRepository extends JpaRepository<Student, Long> {
    List<Student> findByName(String name);
}

Spring Data JPA还提供了更多的查询方法,详情请参阅官方网站

3.3 自定义数据仓库

当Spring Data JPA提供的内置方法无法满足复杂的业务需求时,可以通过自定义数据仓库的方法来扩展原数据仓库。

比如使用自定义的CustomStudentRepository来扩展StudentRepository:

public interface StudentRepository extends JpaRepository<Student, Long>, CustomStudentRepository {
}

CustomStudentRepository中定义如下:

/**
 * 自定义学生仓库,该仓库做为为基础仓库的补充
 */
public interface CustomStudentRepository {
    /**
     * 删除学生
     *
     * @param student 学生
     */
    void deleteStudent(Student student);
}

最后,新建CustomStudentRepositoryImpl类并实现CustomStudentRepository。

public class CustomStudentRepositoryImpl implements CustomStudentRepository {
    @PersistenceContext
    EntityManager entityManager;

    @Override
    public void deleteStudent(Student student) {
        this.entityManager.remove(student);
    }
}

需要注意的是,此实现类的命名必须为: 接口名称 + Impl后缀。因为只有这样Spring Data JPA才能够正确地扫描并使用该实现类。如果你想修改后缀关键字Impl(比如修改为CustomImpl),则可以进行如下配置:

使用XML配置示例:

<repositories base-package="club.codedemo.springdatacomposablerepositories.repository" repository-impl-postfix="CustomImpl" />

JAVA配置示例:

@EnableJpaRepositories(
        basePackages = "club.codedemo.springdatacomposablerepositories.repository",
        repositoryImplementationPostfix = "CustomImpl")

4. 组合多个自定义仓库

在Spring 5版本以前,我们是没有办法将多个自定义仓库组合到一起来使用的。

Spring 5开始支持将多个自定义仓库组合在一起来使用,比如我们再增加一个自定义仓库:

public interface CustomTeacherRepository {
    void deleteTeacherByStudent(Student student);
}

然后便可以采用多实现的方法将CustomTeacherRepository与原CustomStudentRepository组合在一起来使用了:

public interface StudentRepository extends
        JpaRepository<Student, Long>,
        CustomStudentRepository,
        CustomTeacherRepository {

此时StudentRepository的实例将同时拥有CustomStudentRepository以及CustomTeacherRepository定义的方法,并在某个方法方法被调用时,聪明地调用不同的实现类中的具体方法而实现相应的功能。

5. 处理岐义

当我们继承多个接口时,便不可避免的遇到同一个方法被不同的接口重复声明的情况。当这种情况发生时,便在“应该调用哪个具体实现类”上产生了岐义。

比如我们同时在StudentRepository、CustomStudentRepository以及CustomTeacherRepository上分别定义findByIdOrName()方法:

List<Student> findByIdOrName(Long id, String name);

我们以StudentRepository为例,来查看岐义的处理原则。:

public interface StudentRepository❸ extends
        JpaRepository<Student, Long>❹,
        CustomStudentRepository❶,
        CustomTeacherRepository❷ {

当发生岐义时,调用的优先级为:❶ > ❷ > ❸ > ❹

总结:自定义的仓库❶❷中的方法执行优先级最高,所以❶❷ > ❸❹;❶CustomStudentRepository位于❷CustomTeacherRepository之前,所以❶优先级高于❷。在❸StudentRepository中定义的方法优先级高于Spring Data JPA默认实现❹JpaRepository中定义的方法。

6. 总结

本文对Spring Data JPA创建并使用自定义数据仓库进行介绍。我们看到,Spring的自定义仓库模式使得我们可以自由地对数据仓库进行扩展。

创建自定义可组合仓库的模式无疑将对数据仓库的灵活性有大幅的提升。