JPA Criteria 查询:使用数组运算符编写查询
JPA Criteria query : writing query with array operators
我有以下实体
Book {
private String title;
@ManyToMany
@JoinTable(
name = "book_should_read_user",
joinColumns = @JoinColumn(name = "book_id"),
inverseJoinColumns = @JoinColumn(name = "user_id")
)
private Set<User> shouldRead; // the users who should read the book
@OneToMany(mappedBy = "book")
private Set<BookReadAction> actions; // all the read actions performed on the book
}
BookReadAction {
@ManyToOne
private Book book;
@ManyToOne
private User user;
}
现在想查询should read collection中所有用户已经阅读过的所有书籍。 postgres 中的以下 sql 查询可以解决问题:
select *
from book
where id in (
select distinct id
from (
select book.id id,
array_agg(book_should_read_user.user_id) suggested_readers,
array_agg(distinct book_read_action.user_id) read_by
from book b
inner join book_should_read_user on book.id = book_should_read_user.book_id
inner join book_read_action on book.id = book_read_action.book_id
group by book.id) subquery
where suggested_readers <@ read_by)
但是我想以编程方式添加此子句,所以我宁愿使用 JPA 标准 API。尽管我做了一些尝试,但我还是很挣扎。是否可以根据 JPA 条件 API 中的此查询构建谓词?
使用条件 API 不能实现子查询作为 from 子句。
https://www.objectdb.com/java/jpa/query/jpql/from#FROM_and_JOIN_in_Criteria_Queries
您将不得不重组您的查询
您不能将您编写的查询完全写成 HQL,而是等效的查询:
select *
from book b
where exists (
select 1
from book b
inner join book_should_read_user on book.id = book_should_read_user.book_id
inner join book_read_action on book.id = book_read_action.book_id
where b.id = book.id
group by book.id
having array_agg(book_should_read_user.user_id) <@ array_agg(distinct book_read_action.user_id)
)
要使用 HQL 或 JPA 条件编写此查询 API,您需要提供 <@
运算符的自定义实现或通过 SQLFunction
的整体谓词,您可以用你的 Hibernate 方言注册。像这样:
public class ArrayContainsFunction implements SQLFunction {
@Override
public boolean hasArguments() {
return true;
}
@Override
public boolean hasParenthesesIfNoArguments() {
return true;
}
@Override
public Type getReturnType(Type firstArgumentType, Mapping mapping) throws QueryException {
SessionFactoryImplementor sfi = (SessionFactoryImplementor) mapping;
return sfi.getTypeHelper().basic(Integer.class);
}
@Override
public String render(Type firstArgumentType, List args, SessionFactoryImplementor factory) throws QueryException {
return "array_agg(" + args.get(0) + ") <@ array_agg(" + args.get(1) + ") and 1=";
}
}
注册的时候在HQL中应该可以这样使用... HAVING array_contains(shouldRead.id, readAction.id)
我有以下实体
Book {
private String title;
@ManyToMany
@JoinTable(
name = "book_should_read_user",
joinColumns = @JoinColumn(name = "book_id"),
inverseJoinColumns = @JoinColumn(name = "user_id")
)
private Set<User> shouldRead; // the users who should read the book
@OneToMany(mappedBy = "book")
private Set<BookReadAction> actions; // all the read actions performed on the book
}
BookReadAction {
@ManyToOne
private Book book;
@ManyToOne
private User user;
}
现在想查询should read collection中所有用户已经阅读过的所有书籍。 postgres 中的以下 sql 查询可以解决问题:
select *
from book
where id in (
select distinct id
from (
select book.id id,
array_agg(book_should_read_user.user_id) suggested_readers,
array_agg(distinct book_read_action.user_id) read_by
from book b
inner join book_should_read_user on book.id = book_should_read_user.book_id
inner join book_read_action on book.id = book_read_action.book_id
group by book.id) subquery
where suggested_readers <@ read_by)
但是我想以编程方式添加此子句,所以我宁愿使用 JPA 标准 API。尽管我做了一些尝试,但我还是很挣扎。是否可以根据 JPA 条件 API 中的此查询构建谓词?
使用条件 API 不能实现子查询作为 from 子句。
https://www.objectdb.com/java/jpa/query/jpql/from#FROM_and_JOIN_in_Criteria_Queries
您将不得不重组您的查询
您不能将您编写的查询完全写成 HQL,而是等效的查询:
select *
from book b
where exists (
select 1
from book b
inner join book_should_read_user on book.id = book_should_read_user.book_id
inner join book_read_action on book.id = book_read_action.book_id
where b.id = book.id
group by book.id
having array_agg(book_should_read_user.user_id) <@ array_agg(distinct book_read_action.user_id)
)
要使用 HQL 或 JPA 条件编写此查询 API,您需要提供 <@
运算符的自定义实现或通过 SQLFunction
的整体谓词,您可以用你的 Hibernate 方言注册。像这样:
public class ArrayContainsFunction implements SQLFunction {
@Override
public boolean hasArguments() {
return true;
}
@Override
public boolean hasParenthesesIfNoArguments() {
return true;
}
@Override
public Type getReturnType(Type firstArgumentType, Mapping mapping) throws QueryException {
SessionFactoryImplementor sfi = (SessionFactoryImplementor) mapping;
return sfi.getTypeHelper().basic(Integer.class);
}
@Override
public String render(Type firstArgumentType, List args, SessionFactoryImplementor factory) throws QueryException {
return "array_agg(" + args.get(0) + ") <@ array_agg(" + args.get(1) + ") and 1=";
}
}
注册的时候在HQL中应该可以这样使用... HAVING array_contains(shouldRead.id, readAction.id)