JPA CriteriaBuilder ManyToOne 和空值

JPA CriteriaBuilder ManyToOne and null values

我正在尝试创建一个 Specification 来过滤 table。此 table 有 2 个 @ManyToOne 关系(可以为空),我想在其中应用过滤器。比如搜索功能。

假设这个结构

public class X {
   @ManyToOne(targetEntity = A.class, fetch = FetchType.EAGER)
   @JoinColumn(nullable = true, name = "aID")
   private A a;

   @ManyToOne(targetEntity = B.class, fetch = FetchType.EAGER)
   @JoinColumn(nullable = true, name = "bID")
   private B b;
}

还假设 AB 有一个名为 name.

String 属性

所以,我想为 X 创建一个 Specification,我可以在 namename 中匹配 String 值=14=] 或 B.

这是我的方法:

public static Specification<X> filterName(String value) {
   return new Specification<X>() {
      public Predicate toPredicate(Root<X> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
    
         Expression<String> aName = root.get("a").get("name").as(String.class);
         Expression<String> bName = root.get("b").get("name").as(String.class);

         List<Predicate> predicates = new ArrayList<>();

         if (!value.isEmpty()) {
            predicates.add(builder.like(aName, "%" + value + "%"));
            predicates.add(builder.like(bName, "%" + value + "%"));
         }else{
            return null;
         }

         Predicate predicate = builder.or(predicates.toArray(new Predicate[0]));
         return predicate;
      }
   };
}

我真的希望它会起作用,但它没有。

我想要显示那些行,其中 A 的 name OR B 的 namelike 输入值。

如果我启用日志以显示 SQL 查询,这就是我得到的结果:

select xentity0_.aId, xentity0_.bId from table_x xtab cross join table_a atab cross join table_b btab where xtab.aId=atab.id and xtab.bId=btab.id and (atab.name like ?) and (btab.name like ?)

我认为问题出在 where xtab.aId=atab.id and xtab.bId=btab.id 部分。因为 AB 可以为空,所以并不总是满足该条件。

假设 table X:

ID aId bId
1 null 1
2 1 null
3 2 2

这个 table 对于 A:

ID name
1 John
2 Lucy

这个 table 为 B:

ID name
1 Luke
2 Mike

当使用 Specification 寻找 %Lu%X 我想看到的是

ID aId bId
1 null 1
3 2 2

因为 LucyALukeB。相反,我看到的是最后一行(AB 都不为空)

问题是,CriteriaBuilder 默认使用交叉连接(顺便说一句,您提到过)。这就是为什么它不会获取 ABnull 的数据的原因。您可以通过将 Join 类型设置为 LEFT:

来获取这些记录
root.join(Entity.childEntity, JoinType.LEFT)

对于你的情况,尝试如下修改你的代码:

Expression<String> aName = root.join("a", JoinType.LEFT).get("name").as(String.class);
Expression<String> bName = root.join("b", JoinType.LEFT).get("name").as(String.class);

除此之外,我建议对 CriteriaBuilder 使用 MetaModel

要不手动创建和维护元模型 类,您可以使用可用的生成器之一,例如 Hibernate JPA Metamodel GeneratorOpenJPAEclipseLinkDataNucleus .

个人比较喜欢Hibernate JPA 2 Metamodel Generator。要使用它,您唯一需要做的就是将依赖项添加到 pom.xml 文件:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-jpamodelgen</artifactId>
    <version>5.6.5.Final</version>
    <scope>provided</scope>
</dependency>

annotationProcessorPaths 下的以下代码:

 <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
      <annotationProcessorPaths>
        <path>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-jpamodelgen</artifactId>
          <version>5.6.5.Final</version>
        </path>
      </annotationProcessorPaths>
    </configuration>
  </plugin>

之后,当您使用 Maven 构建应用程序时,将自动构建所有 Entitier 的元模型。