Запрос критериев с использованием левого соединения, пользовательского обертки и условий динамического поиска - PullRequest
1 голос
/ 31 марта 2019

У меня 2 сущности Product and 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 и вернуть его с помощью 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();
    }

Текущая ошибка, которая у меня возникла: «Не удается присоединиться к атрибуту базового типа» но я уверен, что у меня более 1 ошибки. Я читал документацию, но я не могу найти пример, который использует соединение, которое возвращает оболочку.

1 Ответ

0 голосов
/ 31 марта 2019
  • используйте cb.construct () для запроса на основе конструктора
  • присоединяйтесь к отзывам, используя имя поля productReviews
  • вместо рецензии Root использовать обзорJoin
  • группировка по всем неагрегированным возвращаемым полям

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