使用 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() );
我的 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()
:
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() );