我怎么能阻止JPA将embeddable对象设置为null只是因为它的所有字段都是null?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我怎么能阻止JPA将embeddable对象设置为null只是因为它的所有字段都是null?相关的知识,希望对你有一定的参考价值。

虽然违反直觉并且显然不是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;
    }
}

我们的想法是,我们可以写像po1.getPeriod().equals(po2.getPeriod())这样的东西。当我们直接访问Date字段时,我们通过额外的间接费用来支付这个费用:po1.getPeriod().getStart()。这通常是一个很好的权衡,但是当我们不得不写po1.getPeriod() == null ? null : po1.getPeriod().getStart()而不是因为我们的JPA提供者喜欢null。

我们怎样才能确保getPeriod()永远不会返回null?遗憾的是,在标准JPA中,既没有Java注释也没有XML设置来解决全局或特定嵌入或嵌入式问题。

答案

Eclipselink的官方解决方案

对于每个受影响的实体,编写一个单独的DescriptorCustomizer实现,如here所述,并使用@Customizer描述的here注释使其处于活动状态。没有技巧我们不能为每个受影响的类使用相同的自定义程序,因为自定义程序需要知道要调用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);
            }
        }
    }
}

要在我们的具体示例中修复null问题,我们必须修改PeriodOwner,如下所示:

@Entity
@Customizer(MyUniversalCustomizer.class)
public class PeriodOwner {
    @Embedded @AttributeOverrides({...})
    private Period period = new Period();
    public Period getPeriod() {
        return period;
    }
}

请注意,除了@Customizer注释之外,我们还使用period初始化字段new Period(),否则新实体仍将具有null 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);
    }
}

请注意,线程安全需要synchronized

对Hibernate的简单攻击

而不是对PeriodPeriodOwner的上述更改,将一个未使用的私有非空字段添加到Period。例如:

@Formula("1")
private int workaroundForBraindeadJpaImplementation;

通过使用@Formula(Hibernate扩展),我们避免在数据库中为此字段添加额外的列。这个解决方案由TomášZáluskýhere描述。对于那些只想在某些情况下改变行为的人来说,这可能很有用。

另一答案

JPA规范完全忽略了对null嵌入对象的处理,并将其留给实现来做他们想做的事情(很好,是吗?)。 It has been requested for JPA 2.2+,但谁知道甲骨文是否会费心去提供。

DataNucleus JPA为嵌入字段/属性提供了2个扩展属性

@Extension(key="null-indicator-column", value="MY_COL")
@Extension(key="null-indicator-value", value="SomeValue")

因此,当嵌入对象为null时,此列将设置为此值,同样在读取对象时,它可以检测到空嵌入对象并将其正确返回给用户。

以上是关于我怎么能阻止JPA将embeddable对象设置为null只是因为它的所有字段都是null?的主要内容,如果未能解决你的问题,请参考以下文章

JPA中EntityListeners注解的使用

可嵌入和 ElementCollection 嵌套

IE9怎么设置阻止弹出窗口

Hibernate/JPA 注释中的多列连接

如何阻止 eclipse 从表中自动创建 JPA 实体?

如何使用 jpa 在 mysql 中存储 java 对象?