1. 概述

在这个简短教程中,我们将讨论Spring Data JPA的一个高级特性:在构建查询时如何连接表。

首先,让我们简要回顾一下JPA Specifications及其用法。

2. JPA Specifications

Spring Data JPA引入了Specification接口,使我们能够使用可重用组件创建动态查询。

本文示例代码将使用AuthorBook类:

@Entity
public class Author {

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

    private String firstName;

    private String lastName;

    @OneToMany(cascade = CascadeType.ALL)
    private List<Book> books;

    // getters and setters
}

为了为Author实体创建动态查询,我们可以使用Specification接口的实现:

public class AuthorSpecifications {

    public static Specification<Author> hasFirstNameLike(String name) {
        return (root, query, criteriaBuilder) ->
          criteriaBuilder.like(root.<String>get("firstName"), "%" + name + "%");
    }

    public static Specification<Author> hasLastName(String name) {
        return (root, query, cb) ->
          cb.equal(root.<String>get("lastName"), name);
    }
}

最后,我们需要AuthorRepository继承JpaSpecificationExecutor

@Repository
public interface AuthorsRepository extends JpaRepository<Author, Long>, JpaSpecificationExecutor<Author> {
}

这样,我们就可以将两个规格串联起来,并使用它们创建查询:

@Test
public void whenSearchingByLastNameAndFirstNameLike_thenOneAuthorIsReturned() {
    
    Specification<Author> specification = hasLastName("Martin")
      .and(hasFirstNameLike("Robert"));

    List<Author> authors = repository.findAll(specification);

    assertThat(authors).hasSize(1);
}

3. 使用JPA Specifications连接表

根据我们的数据模型,Author实体与Book实体之间存在一对多关系:

@Entity
public class Book {

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

    private String title;

    // getters and setters
}

Criteria Query API(Spring Data Criteria Queries)允许我们在创建Specification时连接这两个表,从而可以在查询中包含Book实体的字段:

public static Specification<Author> hasBookWithTitle(String bookTitle) {
    return (root, query, criteriaBuilder) -> {
        Join<Book, Author> authorsBook = root.join("books");
        return criteriaBuilder.equal(authorsBook.get("title"), bookTitle);
    };
}

现在,让我们将这个新的规格与之前创建的规格结合起来:

@Test
public void whenSearchingByBookTitleAndAuthorName_thenOneAuthorIsReturned() {

    Specification<Author> specification = hasLastName("Martin")
      .and(hasBookWithTitle("Clean Code"));

    List<Author> authors = repository.findAll(specification);

    assertThat(authors).hasSize(1);
}

最后,让我们查看生成的SQL并看到JOIN子句:

select 
  author0_.id as id1_1_, 
  author0_.first_name as first_na2_1_, 
  author0_.last_name as last_nam3_1_ 
from 
  author author0_ 
  inner join author_books books1_ on author0_.id = books1_.author_id 
  inner join book book2_ on books1_.books_id = book2_.id 
where 
  author0_.last_name = ? 
  and book2_.title = ?

4. 总结

在这篇文章中,我们学习了如何使用JPA Specifications根据关联实体查询表。

Spring Data JPA的Specifications提供了一种流畅、动态且可重用的方式来创建查询。

如往常一样,源代码可在GitHub上获取。