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()}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/engine/spi/EntityEntry.java#L38], while [{{AbstractEntityTuplizer.getPropertyValues(Object)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/tuple/entity/AbstractEntityTuplizer.java#L493] will show {{null}} current values because [{{NonIdentifierAttribute.isLazy()}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/tuple/NonIdentifierAttribute.java#L17] returns {{false}}.
This causes a problem in [{{DefaultFlushEntityEventListener.dirtyCheck(FlushEntityEvent)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java#L465], where the two different representations are stored in the following two variables: {code:java} final Object[] loadedState = entry.getLoadedState(); final Object[] values = event.getPropertyValues(); {code} 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)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java#L4079], and then forwarded to [{{TypeHelper.findDirty(NonIdentifierAttribute\[\] properties, Object\[\] currentState, Object\[\] previousState, boolean\[\]\[\] includeColumns, boolean anyUninitializedProperties, SessionImplementor session)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java#L282]. 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)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java#L143]), 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()}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/tuple/NonIdentifierAttribute.java#L17] returning {{false}} in {{AbstractEntityTuplizer.getPropertyValues(Object)}}, causing the current state array to hold {{null}} instead of {{LazyPropertyInitializer.UNFETCHED_PROPERTY}}. These different values are also visible in [{{Interceptor.findDirty ( Object, Serializable, Object\[\], Object\[\], String\[\], Type\[\])}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/Interceptor.java#L198]'s {{currentState}} and {{previousState}} arrays. ( 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)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java#L125] - [{{DefaultFlushEntityEventListener.isUpdateNecessary(FlushEntityEvent, boolean)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java#L211] - [{{DefaultFlushEntityEventListener.dirtyCheck(FlushEntityEvent)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java#L465] - [{{AbstractEntityPersister.findDirty(Object\[\], Object\[\], Object, SessionImplementor)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java#L4079] - [{{TypeHelper.findDirty(NonIdentifierAttribute\[\], Object\[\], Object\[\], boolean\[\]\[\], boolean, SessionImplementor)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java#L282] - [{{Type.isDirty(Object, Object, boolean\[\], SessionImplementor)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/type/Type.java#L263] - [{{Type.isDirty(Object, Object, SessionImplementor)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/type/Type.java#L249] - [{{Type.isSame(Object, Object)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/type/Type.java#L167] - [{{Type.isEqual(Object, Object)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/type/Type.java#L184]
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)}}|https://github.com/hibernate/hibernate-orm/blob/5.0.8/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java#L465] uses a different code path in that case.
My original {{hibernate-enhance-maven-plugin}} configuration: {code:xml} <configuration> <enableLazyInitialization>true</enableLazyInitialization> <enableDirtyTracking>false</enableDirtyTracking> <enableAssociationManagement>false</enableAssociationManagement> <enableExtendedEnhancement>false</enableExtendedEnhancement> </configuration> {code} |
|