Reputation: 23
I have the following entities
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;
}
Now I want to query all books that have been read by all the users in the should read collection. The following sql query in postgres does the trick :
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)
I however want to add this clause programmatically so i'd rather use JPA criteria API. Despite some attempts i struggled. Is it possible to build a Predicate from this query in JPA criteria API?
Upvotes: 2
Views: 2112
Reputation: 16400
You can't exactly write the query you wrote as HQL but an equivalent one:
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)
)
To write this query with HQL or the JPA Criteria API you will need to provide a custom implemention of the <@
operator or the overall predicate through a SQLFunction
that you can register in your Hibernate dialect. Something like this:
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=";
}
}
When registering it, you should be able to use like this in HQL ... HAVING array_contains(shouldRead.id, readAction.id)
Upvotes: 2
Reputation: 1292
With criteria API you cannot implement a subquery as from clause.
https://www.objectdb.com/java/jpa/query/jpql/from#FROM_and_JOIN_in_Criteria_Queries
You would have to restructure your query
Upvotes: 0