使用 JPA Criteria 省略不需要的 JOIN

Omit unrequired JOIN with JPA Criteria

我的 Sping Boot 应用程序中有三个相关实体 Author、Category 和 Book:

@Entity
class Category {
    @Id
    Long id;
    // other members, getters and setters
}

@Entity
class Author {
    @Id
    Long id;
    // other members, getters and setters
}

@Entity
class Book {
    @Id
    Long id;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "author_id", nullable = false)
    private Author author;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "category_id", nullable = false)
    private Category category;

    // getters and setters
}

用户可以按作者 and/or 类别搜索书籍。为此,我的应用程序提供了一个接收以下 SearchDto 的搜索 REST 端点:

class SearchDTO {
    public List<Long> authorIds;
    public List<Long> categoryIds;
}

我正在使用条件 API 编写数据库查询。

class BookRepositoryCustomImpl implements BookRepositoryCustom {
    @PersistenceContext
    private EntityManager entityManager;

    public search(SearchDto searchDto) {
        var cb = entityManager.getCriteriaBuilder();
        var query = cb.createQuery(Book.class);
        var result = query.from(Book.class);
        var predicates = new ArrayList<Predicate>();

        if (searchDto.authorIds != null) {
            // Version 1
            // predicates.add(book.get(Book_.author).in(searchDto.authorIds));

            // Version 2
            // var authorJoin = book.join(Book_.author);
            // predicates.add(authorJoin.get(Author_.id).in(searchDto.authorIds));
        }

        // Similar code for category constraint.

        query.select(result);
        query.where(predicates.toArray(Predicate[]::new));
        return entityManager.createQuery(query).getResultList();
    }
}

我的两个版本都有效,但各有缺点。 版本 1 省略了不必要的 Author 连接,直接使用书的列 author_id。但是,由于 id 是数值,因此 SQL 语句包含文字,但我想要绑定变量。 设置 属性 spring.jpa.properties.hibernate.criteria.literal_handling_mode=BIND 时,出现运行时异常,因为提供了一个 Long 变量,但需要一个 Author 对象。在 SQL 语句中使用文字时不会发生此错误。

在版本 2 中,我可以使用绑定变量,但 SQL 语句包含对作者 table (INNER JOIN author ON book.author_id = author.id WHERE author.id IN ()).

的不必要连接

我还可以想到第 3 版,我会使用 authorRepository.getOne(authorId) 获取作者对象的代理并使用 List<Author>。这种方法的缺点是我必须编写所有胶水代码才能将 id 转换为实体对象。

是否有适当的方法来省略 JOIN 但能够使用绑定变量?

您可以使用 getReference():

将 id 转换为对 Author 的引用
Author reference = entityManager.getReference(Author.class, authorId);

我不确定 searchDTO.authorIds 的类型是什么,但假设是一个集合:

List<Author> authors = searchDTO.autorhIds.stream()
         .map( id -> entityManager.getReference(Author.class, id) )
         .collect( Collectors.toList() );

现在您有了一个作者列表,您可以将其用作选项 1 的参数。 这将在数​​据库中创建对作者的惰性引用,实际上没有 查询数据库。

更新:我刚刚意识到 geOne()getReference() 是同一回事,所以你也可以这样写:

List<Author> authors = searchDTO.autorhIds.stream()
         .map( authorRepository::getOne )
         .collect( Collectors.toList() );