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;
}
还假设 A
和 B
有一个名为 name.
的 String
属性
所以,我想为 X
创建一个 Specification
,我可以在 name 的 name 中匹配 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 的 name 是 like
输入值。
如果我启用日志以显示 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
部分。因为 A
或 B
可以为空,所以并不总是满足该条件。
假设 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
因为 Lucy 在 A
和 Luke 在 B
。相反,我看到的是最后一行(A
和 B
都不为空)
问题是,CriteriaBuilder
默认使用交叉连接(顺便说一句,您提到过)。这就是为什么它不会获取 A
或 B
为 null
的数据的原因。您可以通过将 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 Generator
、OpenJPA
、EclipseLink
或 DataNucleus
.
个人比较喜欢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 的元模型。
我正在尝试创建一个 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;
}
还假设 A
和 B
有一个名为 name.
String
属性
所以,我想为 X
创建一个 Specification
,我可以在 name 的 name 中匹配 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 的 name 是 like
输入值。
如果我启用日志以显示 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
部分。因为 A
或 B
可以为空,所以并不总是满足该条件。
假设 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 |
因为 Lucy 在 A
和 Luke 在 B
。相反,我看到的是最后一行(A
和 B
都不为空)
问题是,CriteriaBuilder
默认使用交叉连接(顺便说一句,您提到过)。这就是为什么它不会获取 A
或 B
为 null
的数据的原因。您可以通过将 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 Generator
、OpenJPA
、EclipseLink
或 DataNucleus
.
个人比较喜欢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 的元模型。