Skip to content
Advertisement

EclipseLink not tracking changes to converted wrapper attributes

I am having problems with EclipseLink change tracking in one of my entity classes in a Java SE application. I am using Java 8, JPA 3.0 provided by EclipseLink 3.0.2 and HyperSQL 2.6.1. So far I have kept my implementation provider-independent, so switching JPA providers is an option, although not preferable.

This particular entity class has ~10 attributes of the type OverriddenValue, each of which is a wrapper for 1. a reference to a particular global configuration value, and 2. an optional custom value which will override the global value if present.

public class OverriddenValue<T> {

    @Nullable
    private T customValue;
    private final OverridableValue<?, T> globalConfigValue;

    [...]

}

This class contains getter and setter logic which would make it very inconvenient to store the custom values in the entity directly. So I need these custom values to be wrapped.

Each one of these OverriddenValues in my entity class I have marked with @Convert using a unique AttributeConverter. All of these AttributeConverters simply return the custom value for the Java -> DB mapping, and for the DB -> Java mapping they reconstruct the object with the correct global configuration OverridableValue. It is because I need the reference to the OverridableValue that I did not implement OverriddenValue as an @Embeddable – I would have either have had to persist the ID of the global configuration value, or make it transient, and I decided that was too inconvenient. Besides, each OverriddenValue really only needs one column in the database to store its custom value or null, and so @Convert should be up to the job.

My problem is that EclipseLink does not detect and persist changes to these objects. In a managed instance of this entity, a change to a basic String attribute will be automatically detected and persisted at the next call to EntityManager#flush, but a change to the customValue of an OverriddenValue will not, and these columns in the database will remain as they were.

I looked up how EclipseLink’s change tracking works and found someone saying that it uses .hashCode() and .equals() to determine if an attribute has changed. So I manually implemented these in the OverriddenValue class, but they must have been wrong since the changes are still not being detected.

Momentarily abandoning provider-independence, I tried marking this entity with EclipseLink’s @ChangeTracking annotation and changing the ChangeTrackingType to DEFERRED, but this only caused already-detected changes to be delayed and did not enable detection of any new ones. The other tracking types (ATTRIBUTE and OBJECT) require the entity to implement a particular interface ChangeTracker which seems like it could be helpful, but I don’t quite understand how to make it work.

I have also tried setting the property “eclipselink.weaving.changetracking” to false in the persistence.xml file, on the off chance that weaving was causing the issue (I don’t really understand weaving). No luck.

As a possible workaround, I could manually merge all entities of this type on application shutdown and force overwrite the values in the database. But I consider this a hack and would like to avoid it if at all possible. I feel like the ORM provider should be capable of detecting wrapped attribute changes. Does anyone have an idea where I might look next to try and fix this?

EDIT: Here is an example of what the converter classes all look like:

@Converter
public class FooConverter implements AttributeConverter<OverriddenValue<Integer>, Integer> {

    @Override
    default Integer convertToDatabaseColumn(OverriddenValue<Integer> attribute) {
        return attribute.getCustomValue();
    }

    @Override
    public OverriddenValue<Integer> convertToEntityAttribute(Integer dbData) {
        return Config.GLOBAL_CONFIG_VALUE.override(dbData); // Every converter references a different global variable
    }
}

The method override is just an OverriddenValue factory method.

Advertisement

Answer

Try marking the mapping as mutable:

@Entity
public class YourClass {
  ..
  @Convert("yourConverter")
  @Mutable
  private OverriddenValue value1;
  ..
}

Alternatively, you might modify your own save methods to clone and set the OverriddenValue instance when you know there are changes within it to be persisted.

  YourClass instance = em.find(id, YourClass.class);
  instance.setValue1(instance.getValue1().clone());
  instance.getValue1().setCustomValue(value)
  em.commit();
4 People found this is helpful
Advertisement