This is more of a solution than a problem reproduced here to help someone else.
Situation:
You have a one to many association mapped as a collection. You have a Struts2 type converter for the collection property that constructs a new collection which the Struts2 parameters interceptor then calls the setter with. The collection is mapped with cascade=all-delete-orphan to ensure that when objects are deleted from the collection they are deleted from the database.
Problem:
When replacing the collection the old entries are not being deleted from the database but rather the new entries are being added to what is already there leading to duplicate rows.
What is happening:
When Hibernate initializes the object containing the collection it calls the setter method with a Hibernate enhanced collection that tracks when items are removed from the collection. When Struts2 calls the setter with the collection created in the type converter it replaces the Hibernate enhanced collection which then goes out of scope and is garbage collected meaning no deletions are tracked. When Hibernate comes to commit the transaction it sees an un-enhanced collection which it assumes means that it needs to insert the entries.
Incorrect Solution 1:
The first solution I tried was to change the setter method on the class to delete all of the entries in the current collection and then add the new ones rather than replacing the collection class. This doesn't work because Hibernate itself calls the setter when loading the class to put the enhanced collection in. By not replacing the default empty collection that is created when the class loads the class never ends up with an enhanced collection and so never tracks the deletes. This leads to the same result.
Incorrect Solution 2:
The second solution I tried was to initialize the collection to an empty collection on object load and then only replace it if its size was 0. If the size wasn't 0 I deleted the old objects and added the new ones to the existing collection. This worked OK for some of the cases but in the situation when the collection was empty when Hibernate loaded it the code did a replacement of the collection which turned out to be a bad thing. This caused the empty enhanced collection to be garbage collected which shouldn't matter to the final result - there is nothing to delete after all - but Hibernate places checks for enhanced collections that manage all-delete-orphan relationships and throws exceptions when they go out of scope. Not a good solution.
Working solution:
What I needed was a way to distinguish between when Hibernate was setting the collection and when the Struts2 Parameters Interceptor was. I didn't want to do anything like trying to examine the runtime class of the collection being passed to the setter as that would introduce ugly Hibernate dependencies into my POJO. As described above checking the size was out also. The solution was that if the collection was null I replaced it and if it was not null I updated it. Sample code for the setter below:
Code:
/**
* @param objects the objects to set
*/
public void setObjects(List<TrafficUnitObject> objects)
{
if (this.objects == null) //already initialised
{
this.objects = objects;
}
else
{
while (this.objects.size() > 0)
{
this.objects.remove(0);
}
this.objects.addAll(objects);
}
}
If the object is empty and Hibernate is initializing it the collection will be null so it will be replaced by the Hibernate enhanced collection. If the object has been initialized with an enhanced collection then the collection will be non null and we will delete the existing objects and set the new objects. Because the collection is enhanced at this point Hibernate will track the additions and deletions. This means that the collection must be initialized to null on object creation and that clients of the class need to be aware that this is possible for objects that have not yet been saved to the database.