尽管JPA标准违反直觉并且显然不是必需的,但是Eclipselink和Hibernate都竭尽全力创建以下NullPointerExceptions源:当实体中嵌入的对象的所有字段均为null时,它们将字段本身替换为null。这是一个简化的示例:
@Embeddable
public class Period {
private Date start;
public Date getStart() {
return start;
}
private Date end;
public Date getEnd() {
return end;
}
public boolean equals(Period other) { // TODO: implement hashCode()!
return Objects.equals(start, other.start) && Objects.equals(end, other.end);
}
}
@Entity
public class PeriodOwner {
@Embedded @AttributeOverrides({...})
private Period period;
public Period getPeriod() {
return period;
}
}
Run Code Online (Sandbox Code Playgroud)
这个想法是我们可以写类似的东西po1.getPeriod().equals(po2.getPeriod())。当Date直接访问字段时,我们通过间接的方式支付此费用:po1.getPeriod().getStart()。这通常是一个很好的权衡,但是当我们不得不编写时却po1.getPeriod() == null ? null : po1.getPeriod().getStart()不是,因为我们的JPA提供程序喜欢null。
我们如何确保getPeriod()永不返回null?不幸的是,在标准的JPA中,既没有Java注释也没有XML设置来全局或特定嵌入或可嵌入对象解决此问题。
小智 6
Eclipselink 官方解决方案
对于每个受影响的实体,DescriptorCustomizer按照此处所述编写单独的实现,并使用此处所述的@Customizer注释使其处于活动状态。如果没有技巧,我们就无法为每个受影响的类使用相同的定制器,因为定制器需要知道要调用的可嵌入字段的名称。setIsNullAllowed(false)
这是一个通用DescriptorCustomizer实现,可用于使固定的类非空列表中的每个可嵌入:
public class MyUniversalDescriptorCustomizer implements DescriptorCustomizer {
private static final ImmutableSet<Class> NON_NULLABLE_EMBEDDABLES = ImmutableSet.of(Period.class);
@Override
public void customize(ClassDescriptor cd) throws Exception {
Class entityClass = cd.getJavaClass();
for (Field field : entityClass.getDeclaredFields()) {
if (NON_NULLABLE_EMBEDDABLES.contains(field.getType())) {
System.out.println(field.getName());
AggregateObjectMapping aom = (AggregateObjectMapping) cd.getMappingForAttributeName(field.getName());
aom.setIsNullAllowed(false);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
为了解决我们具体例子中的空问题,我们必须修改PeriodOwner如下:
@Entity
@Customizer(MyUniversalCustomizer.class)
public class PeriodOwner {
@Embedded @AttributeOverrides({...})
private Period period = new Period();
public Period getPeriod() {
return period;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,除了@Customizer注释之外,我们还初始化字段period,new Period()因为否则新实体仍将具有空period字段。
Hibernate的官方解决方案
显然,由于 Hibernate 5.1 有一个设置hibernate.create_empty_composites.enabled. (因为我没有使用 Hibernate,所以我没有试图找出这个设置的去向。)
行人解决方法
下面解决了这个问题,并没有过多地污染代码,但它仍然相当混乱。
@Embeddable
public class Period {
private Date start;
public Date getStart() {
return start;
}
private Date end;
public Date getEnd() {
return end;
}
public boolean equals(Period other) { // TODO: implement hashCode()!
return Objects.equals(start, other.start) && Objects.equals(end, other.end);
}
public static Period orNew(Period period) {
return period != null ? period : new Period();
}
}
@Entity
public class PeriodOwner {
@Embedded @AttributeOverrides({...})
private Period period;
public synchronized Period getPeriod() {
return period = Period.orNew(period);
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,这synchronized是线程安全所必需的。
Hibernate 的简单破解
代替上述对Periodand 的更改,向 中PeriodOwner添加一个未使用的私有非空字段Period。例如:
@Formula("1")
private int workaroundForBraindeadJpaImplementation;
Run Code Online (Sandbox Code Playgroud)
通过使用@Formula(一个 Hibernate 扩展),我们避免在数据库中为此字段添加额外的列。Tomáš Záluský在这里描述了这个解决方案。对于那些只想在某些情况下改变行为的人来说,它可能很有用。
| 归档时间: |
|
| 查看次数: |
2902 次 |
| 最近记录: |