JPA规范+多对多字段的动态排序,无重复

Vla*_*huk 3 java jpa

我有一个包含很多字段、多对一和多对多关系的类,我需要通过一些列(来自这个类和来自连接类的字段)添加动态过滤器,并按这些字段添加排序好。

我在按多对多字段过滤和排序时遇到问题

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

我有规范通过 EntityB.name 过滤 EntityA(我设置了 criteriaQuery.distinct(true) 以防止重复,我没有这个)

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() + "%";
}
}
Run Code Online (Sandbox Code Playgroud)

它工作正常,问题是当我尝试同时使用排序和此过滤器时

entityARepository.findAll(EntityASpecifications.entityBNameContains("name"), PageRequest.of(page, size, Sort.Direction.ASC, sortColumnName));
Run Code Online (Sandbox Code Playgroud)

对于作为 EntityB.name 连接到 EntityA 的字段,它失败(我还有一些其他字段与 @ManyToOne 失败),并出现下一个异常:

Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: ORDER BY items must appear in the select list if SELECT DISTINCT is specified
Run Code Online (Sandbox Code Playgroud)

如果我删除criteriaQuery.distinct(true); 一切都会好的,但我会有重复的,我不想拥有它们

如何修复它并且同时没有重复的结果?

Rez*_*iri 6

您获得 EntityA 重复结果的原因是您将 EntityA 与 EntityB 连接在一起,并且您的谓词基于 EntityB 中的一个字段。因此,如果 EntityB 的多个条目满足您的条件,并且它们都属于同一个 EntityA,您将获得多个 EntityA 的条目。因此,解决方案是使用“exists”而不是连接两个表,并且您不再需要使用 distinct 了。您的规格可能如下所示:

 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() + "%";
 }
} 
Run Code Online (Sandbox Code Playgroud)