| When setting the enableLazyInitialization parameter of the hibernate-enhance-maven-plugin's configuration to true, the non-identifier attributes annotated with @Basic(fetch = FetchType.LAZY) will show as LazyPropertyInitializer.UNFETCHED_PROPERTY in EntityEntry.getLoadedState(), while AbstractEntityTuplizer.getPropertyValues(Object) will show null current values because NonIdentifierAttribute.isLazy() returns false. This causes a problem in DefaultFlushEntityEventListener.dirtyCheck(FlushEntityEvent), where the two different representations are stored in the following two variables:
final Object[] loadedState = entry.getLoadedState();
final Object[] values = event.getPropertyValues();
And then (when there is no findDirty() interceptor override, and the entity is not an instance of SelfDirtinessTracker, so the enableDirtyTracking parameter of the hibernate-enhance-maven-plugin configuration is false), the dirty check is performed by AbstractEntityPersister.findDirty(Object[] currentState, Object[] previousState, Object entity, SessionImplementor session), and then forwarded to TypeHelper.findDirty(NonIdentifierAttribute[] properties, Object[] currentState, Object[] previousState, boolean[][] includeColumns, boolean anyUninitializedProperties, SessionImplementor session). This checks if the current state holds LazyPropertyInitializer.UNFETCHED_PROPERTY, but it holds null instead (as described in the first paragraph). These two values are then compared in the appropriate Type subclass' isDirty(), isSame() and isEqual() methods, which (depending on the actual implementation) either return an incorrect dirty state (and then an unnecessary UPDATE is scheduled in DefaultFlushEntityEventListener.onFlushEntity(FlushEntityEvent)), or throw an exception (e.g. ClassCastException when comparing a primitive byte array to LazyPropertyInitializer.UNFETCHED_PROPERTY. In my opinion, the problem lies in NonIdentifierAttribute.isLazy() returning false in AbstractEntityTuplizer.getPropertyValues(Object), causing the current state array to hold null instead of LazyPropertyInitializer.UNFETCHED_PROPERTY. (As far as I can tell, no Type subclasses are prepared to handle LazyPropertyInitializer.UNFETCHED_PROPERTY directly, so it shouldn't be propagated to them from TypeHelper.findDirty().) The code path of the error that I've observed:
- DefaultFlushEntityEventListener.onFlushEntity(FlushEntityEvent)
- DefaultFlushEntityEventListener.isUpdateNecessary(FlushEntityEvent, boolean)
- DefaultFlushEntityEventListener.dirtyCheck(FlushEntityEvent)
- AbstractEntityPersister.findDirty(Object[], Object[], Object, SessionImplementor)
- TypeHelper.findDirty(NonIdentifierAttribute[], Object[], Object[], boolean[][], boolean, SessionImplementor)
- Type.isDirty(Object, Object, boolean[], SessionImplementor)
- Type.isDirty(Object, Object, SessionImplementor)
- Type.isSame(Object, Object)
- Type.isEqual(Object, Object)
First I've observed this problem with a UserType field, but I could reproduce it with byte[] too. Setting enableDirtyTracking to true is a possible workaround, as DefaultFlushEntityEventListener.dirtyCheck(FlushEntityEvent) uses a different code path in that case. |