使用左连接、自定义包装器和动态搜索词的条件查询

Criteria Query Using Left Join, Custom Wrapper, and Dynamic search terms

我有 2 个实体 Product 和 Review 以及 1 个接口 ReviewItem

@Entity
public class Product
{
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long productid;

    private String productname;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "reviewedProduct")
    private Set<Review> productReviews;
    ... constructor, getters and setters
} 
@Entity
public class Review
{
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long reviewid;

    Integer stars;

    @ManyToOne
    @JoinColumn(name = "productid")
    private Product reviewedProduct;
    ... constructor, getters and setters
}
public interface ReviewItem
{
    long getProductid();

    String getProductname();

    Double getAvgRating();

    Integer getReviewCount();
}

简而言之,我正在尝试编写如下所示的条件查询:

SELECT p.productid, p.productname, AVG(r.stars) AS avgRating, 
COUNT(r.productid) AS reviewCount 
FROM products p 
LEFT JOIN reviews r ON p.productid=r.productid 
WHERE p.productname LIKE "%bean%" AND p.productname LIKE "%lemon%"
GROUP BY p.productid, p.productname
ORDER BY avgRating DESC

但是,LIKE 搜索词是动态的,因此我必须使用 Criteria Builder。我可以在没有连接的情况下编写条件查询:

public List<Product> dynamicQueryWithStringsLike(Set<String> searchSet
{
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Product> query = cb.createQuery(Product.class);
        Root<Product> product = query.from(Product.class);

        Path<String> productPath = product.get("productname");

        List<Predicate> predicates = new ArrayList<>();
        for (String word : searchSet)
        {
            word = "%" + word + "%";
            predicates.add(cb.like(productPath, word));
        }

        query.select(product)
                .where(cb.and(predicates
                .toArray(new Predicate[predicates.size()])));

        return entityManager.createQuery(query)
                .getResultList();
}

但我不太确定如何使用 LEFT JOIN return 使用 Wrapper。 到目前为止我有:

public List<ReviewItem> testCriteria(Set<String> searchSet)
    {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<ReviewItem> query = cb.createQuery(ReviewItem.class);
        Root<Product> productRoot = query.from(Product.class);
        Root<Review> reviewRoot = query.from(Review.class);
        Path<String> productPath = productRoot.get("productname");

        List<Predicate> predicates = new ArrayList<>();
        for (String word : searchSet)
        {
            word = "%" + word + "%";
            predicates.add(cb.like(productPath, word));
        }

        Join<Product, Review> productReviewJoin = productRoot
                .join("productid", JoinType.LEFT);

        query
            .multiselect(
                productRoot.get("productid"), 
                productRoot.get("productname"),
                cb.avg(reviewRoot.get("stars")), 
                cb.count(reviewRoot.get("productid"))
                )
            .where(cb.and(predicates.toArray(new Predicate[predicates.size()])));

        return entityManager.createQuery(query)
                .getResultList();
    }

我目前的错误是"Cannot join to attribute of basic type" 但我确定我有不止 1 个错误。我一直在阅读文档,但似乎找不到使用 return 包装器的 Join 的示例。

  • 使用 cb.construct() 进行基于构造函数的查询
  • 使用字段名称 productReviews 加入评论
  • 而不是 reviewRoot 使用 reviewJoin
  • 按所有非聚合返回字段分组

    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<ReviewItem> query = cb.createQuery(ReviewItem.class);
    Root<Product> productRoot = query.from(Product.class);
    ...
    Join<Product, Review> reviewJoin = productRoot.join("productReviews", JoinType.LEFT);
    
    query.select(cb.construct(ReviewItem.class,        
                    productRoot.get("productid"),
                    productRoot.get("productname"),
                    cb.avg(reviewJoin.get("stars")),
                    cb.countDistinct(reviewJoin)
            ))
            .where(cb.and(predicates.toArray(new Predicate[predicates.size()])))
            .groupBy(productRoot.get("productid"), productRoot.get("productname"))
            .orderBy(cb.desc(cb.avg(reviewJoin.get("stars"))));
    
    List<ReviewItem> x = entityManager.createQuery(query).getResultList();
    

评论项目:

public class ReviewItem {

Long getProductid;
String getProductname;
Double getAvgRating;
Long getReviewCount;

public ReviewItem(Long getProductid, String getProductname, Double getAvgRating, Long getReviewCount) {
    this.getProductid = getProductid;
    this.getProductname = getProductname;
    this.getAvgRating = getAvgRating;
    this.getReviewCount = getReviewCount;
}