How can I prevent JPA from setting an embeddable object to null just because all its fields are null?

前端 未结 2 1764
再見小時候
再見小時候 2021-02-14 08:00

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

相关标签:
2条回答
  • 2021-02-14 08:20

    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.

    0 讨论(0)
  • 2021-02-14 08:30

    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.

    0 讨论(0)
提交回复
热议问题