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)