【问题标题】:JPA CriteriaBuilder ManyToOne and null valuesJPA CriteriaBuilder ManyToOne 和空值
【发布时间】:2021-12-06 01:14:19
【问题描述】:

我正在尝试创建一个Specification 来过滤表格。该表有 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 有一个名为nameString 属性。

所以,我想为X 创建一个Specification,我可以在AB名称 中匹配String 值。

这是我的方法:

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 ORname 和 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.名字之类的?)

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

假设此表为X

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

此表为A

ID name
1 John
2 Lucy

此表为B

ID name
1 Luke
2 Mike

当使用 Specification 查找 %Lu% 时,我希望看到的是 X

ID aId bId
1 null 1
3 2 2

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

【问题讨论】:

    标签: spring-boot jpa criteria-api many-to-one


    【解决方案1】:

    问题在于,CriteriaBuilder 默认使用交叉连接(您提到的关于 BTW 的内容)。这就是为什么它不会得到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);
    

    除此之外,我建议将MetaModel 用于CriteriaBuilder

    要不手动创建和维护元模型类,您可以使用可用的生成器之一,例如 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 构建应用程序时,将自动构建所有实体的元模型。

    【讨论】:

    • 感谢您的回答!我确实按照您提到的方式通过左连接解决了它。无论如何,我会接受您的回答,以进一步参考其他人。关于 MetaModel,我之前尝试过使用它们,但我需要为每个实体手动创建该元模型。有没有办法自动创建它?并保持更新?
    • @sebasira 当然有。我已经编辑了我的答案(添加了一些关于元模型生成器的信息)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-14
    • 1970-01-01
    • 2017-07-02
    • 2015-02-21
    • 2022-11-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多