首页 > 解决方案 > 使用 JPA 标准省略不需要的 JOIN

问题描述

我的 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
}

用户可以按作者和/或类别搜索书籍。为此,我的应用程序提供了一个接收以下 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 语句中使用文字时不会发生此错误。

INNER JOIN author ON book.author_id = author.id WHERE author.id IN ($1)在版本 2 中,我可以使用绑定变量,但 SQL 语句包含对 Author 表 ( )的不必要连接。

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

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

标签: hibernatejpaspring-datacriteria-api

解决方案


您可以将 id 转换为对Authorwith的引用getReference()

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() );

推荐阅读