Although counterintuitive and apparently not required by the JPA standard, both Eclipselink and Hibernate go to great lengths to create the following source of NullPointerExcept
Official solution for Eclipselink
For each affected entity write a separate DescriptorCustomizer
implementation as described here and make it active using the @Customizer
annotation as described here. Without tricks we cannot use the same customizer for every class affected, as the customizer needs to know the names of the embeddable fields for which it is to invoke setIsNullAllowed(false)
.
Here is a universal DescriptorCustomizer
implementation that can be used to make every embeddable from among a fixed list of classes non-nullable:
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);
}
}
}
}
To fix the null problem in our specific example, we have to modify PeriodOwner
as follows:
@Entity
@Customizer(MyUniversalCustomizer.class)
public class PeriodOwner {
@Embedded @AttributeOverrides({...})
private Period period = new Period();
public Period getPeriod() {
return period;
}
}
Note that in addition to the @Customizer
annotation, we also initialize the field period
with new Period()
because otherwise new Entities would still have null period
fields.
Official solution for Hibernate
Apparently, since Hibernate 5.1 there is a setting hibernate.create_empty_composites.enabled
. (Since I am not using Hibernate, I didn't try to find out where this setting goes.)
Pedestrian workaround
The following takes care of the problem without polluting the code too much, but it's still quite messy.
@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);
}
}
Note that synchronized
is required for thread safety.
Simple hack for Hibernate
Instead of the above changes to Period
and PeriodOwner
, add one unused private non-null field to Period
. For example:
@Formula("1")
private int workaroundForBraindeadJpaImplementation;
By using @Formula
(a Hibernate extension) we avoid adding an extra column for this field in the database. This solution was described by Tomáš Záluský here. It may be useful for those who want to change the behaviour only in some cases.
The JPA spec totally ignores handling of null embedded objects and leaves it up to implementations to do what they feel like (nice, yes?). It has been requested for JPA 2.2+ but who knows if Oracle will ever bother to provide that.
DataNucleus JPA provides 2 extension properties for an embedded field/property
@Extension(key="null-indicator-column", value="MY_COL")
@Extension(key="null-indicator-value", value="SomeValue")
and so when the embedded object is null then this column is set to this value, and likewise when reading in objects it can detect a null embedded object and return it correctly to the user.