JPA Specification + dynamic sorting for many-to-many field without duplicates

Vladyslava Shevchuk :

I have a class with a lot of fields, many-to-one and many-to-many relationship and I need to add dynamic filters by some columns (from this class and by fields from connected class) and add sorting by those fields as well.

I have an issue with filtering and sorting by many-to-many field

@Entity
class EntityA {
...
@ManyToMany
@JoinTable (
        name = "EntityA_EntityB",
        joinColumns = { @JoinColumn(name = "EntityB") },
        inverseJoinColumns = { @JoinColumn(name = "entityb_id") }
)
private List<EntityB> bEntities;
...
}

And I have Specification to filter EntityA by EntityB.name (I set criteriaQuery.distinct(true) to prevent duplicates, which I have without this)

public class EntityASpecifications {
//other specifications
...

public static Specification<EntityA> entityBNameContains(String query) {
    return (root, criteriaQuery, criteriaBuilder) -> {

        if (query == null) {
            return criteriaBuilder.conjunction();
        }
        criteriaQuery.distinct(true);

        return getContainsPredicate(criteriaBuilder, root.join("bEntities").get("name"), query);
    };
}

private static Predicate getContainsPredicate(CriteriaBuilder criteriaBuilder, Expression<String> field, String query) {

    return (query == null) ? criteriaBuilder.conjunction() : criteriaBuilder.like(criteriaBuilder.lower(field), getContainsPattern(query));
}

private static String getContainsPattern(String searchTerm) {

    return (searchTerm.isEmpty()) ? "%" : "%" + searchTerm.toLowerCase() + "%";
}
}

It works fine, the issue is that when I'm trying to use sorting and this filter at the same time

entityARepository.findAll(EntityASpecifications.entityBNameContains("name"), PageRequest.of(page, size, Sort.Direction.ASC, sortColumnName));

It fails for fields connected to EntityA as EntityB.name (also I have some other fields with @ManyToOne that fail) with the next Exception:

Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: ORDER BY items must appear in the select list if SELECT DISTINCT is specified

If I remove criteriaQuery.distinct(true); everything will be okay, but I will have duplicates and I don't want to have them

How to fix it and don't have duplicate results at the same time?

Reza Nasiri :

The reason you are getting duplicate results for EntityA is that you are joining EntityA with EntityB and your predicate is based on a filed in EntityB. So if there are multiple entries of EntityB that satisfy your condition and they all belong to same EntityA, you will get multiple entries of EntityA. So the solution is to use "exists" instead of joining the two tables and you won't need to use distinct any more. your specification can look like this :

 public class EntityASpecifications {
 //other specifications
 ...

   public static Specification<EntityA> entityBNameContains(String query) {
       return (root, criteriaQuery, criteriaBuilder) -> {

          if (query == null) {
              return criteriaBuilder.conjunction();
          }
          Subquery< EntityB> subquery = query.subquery(EntityB.class);
          Root< EntityB> subqueryRoot = subquery.from(EntityB.class);
          subquery.select(subqueryRoot);

          subquery.where(criteriaBuilder.and(criteriaBuilder.equal(root, subqueryRoot.get("entitya_id")),
                                criteriaBuilder.like(criteriaBuilder.lower("name"), getContainsPattern(query)))
                   );

          return criteriaBuilder.exists(subquery);

       };
   } 
   private static String getContainsPattern(String searchTerm) {
     return (searchTerm.isEmpty()) ? "%" : "%" + searchTerm.toLowerCase() + "%";
 }
} 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=90117&siteId=1