Question

Is it possible to sort child rows of each group (child rows of each parent row) in the desired order and take the desired number of top rows in each group in JPA?

For example, I have three tables in MySQL database.

  • category
  • sub_category
  • product

The relationship between these tables is intuitive - one-to-many in the order in which they appear.

I'm executing the following criteria query on the sub_category table.

CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<SubCategory>criteriaQuery=criteriaBuilder.createQuery(SubCategory.class);
EntityType<SubCategory> entityType = entityManager.getMetamodel().entity(SubCategory.class);
Root<SubCategory> root = criteriaQuery.from(entityType);
criteriaQuery.distinct(true);

Join<SubCategory, Category> catJoin = root.join(SubCategory_.catId, JoinType.INNER);
SetJoin<SubCategory, Product> prodJoin = root.join(SubCategory_.productSet, JoinType.INNER);

List<Predicate>predicates=new ArrayList<Predicate>();
predicates.add(criteriaBuilder.isTrue(root.get(SubCategory_.visible)));
predicates.add(criteriaBuilder.isTrue(catJoin.get(Category_.visible)));
predicates.add(criteriaBuilder.isTrue(prodJoin.get(Product_.visible)));

criteriaQuery.where(predicates.toArray(new Predicate[0]));
criteriaQuery.orderBy(criteriaBuilder.desc(root.get(SubCategory_.subCatId)));               
List<SubCategory> list = entityManager.createQuery(criteriaQuery).getResultList();

This query happens to fetch all sub categories where visible (TINYINT in MySQL which is mapped to a Boolean property in the corresponding JPA entity) is true in all of these tables.

After executing this query, a set of products of each sub category will be available as a Set<Product>.

I want to fetch a group of top five products (not all products of each sub category) of each sub category after sorting a group of products of each sub category in descending order.

Is it possible?

I'm using JPA 2.0 provided by EclipseLink 2.3.2.

Was it helpful?

Solution 2

Unfortunately, AFAIK, the only way to limit the results of a query is to use Query#setMaxResults() and this is only applicable to a Query object, not to a subquery.

I suggest to use a workaround, like selecting only the subcategories with your query. Thanks to lazy loading, when returning the results, JPA shouldn't fetch all products for each result (you can check this with some logging), therefore avoiding an unnecessary db load.

Later, when you need the top 5 products for each subcategory, instead of using

SubCategory sc;
List<Product> list = sc.getProducts();

just run a query for each subcategory:

SubCategory sc;
List<Product> list = subCategoryService.getTopProducts(sc);

This shouldn't have a visible worsening of the performances, if the subcategories shown each time are in a reasonable number (like up to 30, as for the results that can be displayed in a paginated data list).

OTHER TIPS

I'm not familiar with a way to limit a join or a subquery in JPA. If you find some elegant way to do it, please don't forget to post it here.

From what I can think of, you could do something that's less effective :

  1. Remove the Products "Join" - Retrieve only the Categories and Sub categories.
  2. Retrieve the Products of each sub category using a separate query and specify a setMaxResults(5) to every one of your "Products" queries.

Another possibility is using a native query. That would be much more effective but populating your entity beans would be tedious. Here is an example of something you can do to get the top 5 products of each sub category :

select * from category c
inner join sub_category cn on cn.category_id = c.id
inner join product p1 on p1.sub_category_id = cn.id and p1.id in
(select tmp.id from (select p.id, row_number() over (partition by p.sub_category order by p.id) as rn from product p where p.visible = 1) tmp
where tmp.rn < 6);

(Obviously the query needs some tweeking to make it work on)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top