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

时间:2019-03-31 12:15:18

标签: java mysql hibernate criteria-api

我有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 将其返回包装器。 到目前为止,我有:

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();
    }

我当前遇到的错误是“无法加入基本类型的属性” 但我确定我有多个错误。我一直在阅读文档,但似乎找不到使用返回包装器的Join的示例。

1 个答案:

答案 0 :(得分:0)

  • 使用cb.construct()进行基于构造函数的查询
  • 使用字段名称productReviews加入评论
  • 而不是rev​​iewRoot使用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();
    

ReviewItem:

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;
}