Hi,
I'm experiencing an issue which seems an exact behaviour described in https://hibernate.atlassian.net/browse/HHH-1668. The fix version mentioned in 3.2.0rc5, while I'm running on 3.3.2.GA.
The behaviour is as follows:
* Retrieve entity objects (simpel parent-child OneToMany objects) from the DB
* Map these objects to transfer objects
* Map these objects back to entity objects
* Perform an EntityManager.merge on the parent entity (cascading has been enabled).
This triggers an UPDATE command being sent to the DB (I see the @Version column being incremented), although the data has not changed at all.
Debugging through the Hibernate code reveals the following:
1. The parent object is correctly identified as being unchanged
2. The collection of child objects is identified as begin changed (!! incorrectly).
3. The child objects themselves are also unchanged
The fact that the collection of child objects is dirty lest Hibernate think it needs to update the parent, causing the update command be sent.
I've tried to determine the spot where the PersistentSet is marked as dirty(). This is originates from CollectionType#replaceElements(CollectionType.java:501), where Collection.clear() is called. This marks the collection as dirty. Afterwards, in case both collections are PersistentSet, the original collection is being checked for dirtyness. In case it wasn't dirty, the dirty flag on the target collection is cleared (lines 517-523 of the same class).
However, in the case of an EntityManager.merge, the detached object has it's own collection type, in my case being an ordinary java.util.HashSet. In this case the code of course does nothing and the set is marked as dirty (incorrectly!).
HHH-1668 seems to describe the exact same issue, is marked as fixed in an earlier version than my own version. Am I walking into a regression? Was the issue not fixed at all? For larger sets of object trees, this behaviour is causing massive performance hits.
Update: https://hibernate.atlassian.net/browse/HHH-1401 seems also related to this issue.
Update 2: I've just modified CollectionType.java in the following way:
Code:
if (original instanceof PersistentCollection) {
if (result instanceof PersistentCollection) {
if (!((PersistentCollection) original).isDirty()) {
((PersistentCollection) result).clearDirty();
}
}
}
if replaced with
Code:
final boolean collectionsAreEqual = result.equals(original); // <-- This must be executed before result is modified.
if (original instanceof PersistentCollection) {
if (result instanceof PersistentCollection) {
if (!((PersistentCollection) original).isDirty()) {
((PersistentCollection) result).clearDirty();
}
}
} else if (result instanceof PersistentCollection) {
if (collectionsAreEqual) {
((PersistentCollection) result).clearDirty();
}
}
(i.e. the second else-if is added).
In this case, the unnecessary updates are not present anymore. However, I'm no expert on Hibernate code and not able to phantom any regressions this may introduce.. Of course, this fix assumes a correct implementation of equals() and hashCode() on the entity objects, however this is a requirement stated in the Hibernate manual, so this shouldn't be an issue. If these methods are not implemented correctly, the same behaviour as the now is executed.
(Of course, this code (if/else-tree) could be reordered to be cleaner code, if this should be the fix, I've omitted this for clarity reasons)
Update: code fix, the equals check has to be performed before modifying "result", otherwise the result of the equals check is always true. The code can probably be optimized for performance reasons, this is currently not done.