Moving an object from one container to another is a very common maneuver (think cut-and-paste). I have a situation where i would like to use 'cascade="all-delete-orphan"'. This works just fine until i try to move an object from one collection to another, at which time i receive an exception stating "deleted object would be re-saved by cascade". The object in question is not orphaned -- it's in the another collection. If i switch to 'cascade="all"', i create true orphans that remain in the DB anytime i remove an object from a collection for good.
Should this situation be reported as a bug? The "would be re-saved" phrase is ironic, as it is exactly the behavior that i need, since the moved object is not really an orphan.
If this is something around which we must work, has the community declared any best-practices? (Removing the object from collection-1 and adding a clone of it to collection-2 is not an option here; i must deal with the same reference. Also, the DAO layer will continue operate only on the holder of the collections, not on any of the objects lower in the containment hierarchy.)
A very simplified (and not entirely analogous) example follows.
Code:
/** An object that has two Hands. */
public class Person
{
private Long id;
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
private Hand leftHand;
public Hand getLeftHand() { return leftHand; }
private Hand rightHand;
public Hand getRightHand() { return rightHand; }
public Person() { this("Jane Doe"); }
public Person(String name)
{
this.name = name;
leftHand = new Hand("Left Hand");
rightHand = new Hand("Right Hand");
}
}
Code:
/** A container of marbles. */
public class Hand
{
private Long id;
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
private List<Marble> marbles;
public List<Marble> getMarbles() { return marbles; }
public Hand() { this ("unnamed"); }
public Hand(String name)
{
this.name = name;
marbles = new ArrayList<Marble>();
}
}
Code:
/** A simple object that can be held in a Hand. */
public class Marble
{
private Long id;
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Marble() { this("unnamed"); }
public Marble(String name) { this.name = name;}
}
Code:
/** Program to move marbles from one hand to another. */
public class Main
{
public static void main(String[] args)
{
//Create the objects and save to DB
Person person = new Person();
Hand leftHand = person.getLeftHand();
Hand rightHand = person.getRightHand();
List<Marble> marbles = leftHand.getMarbles();
for (int m=0; m < 10; m+=2)
marbles.add(new Marble("m"+m));
marbles = rightHand.getMarbles();
for (int m=1; m < 10; m+=2)
marbles.add(new Marble("m"+m));
PersonDao dao = new PersonDao();
dao.save(person);
//Remove some marbles from leftHand
leftHand.getMarbles().remove(0);
leftHand.getMarbles().remove(0);
dao.save(person);
//Move a marble from rightHand to leftHand
//THIS IS THE SECTION THAT FAILS IF cascade="all-delete-orphan"
leftHand.getMarbles().add(rightHand.getMarbles().remove(0));
dao.save(person);
}
}
Code:
//DAO layer not shown here
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="marbles">
<class name="Person">
<id name="id" type="long" access="field">
<generator class="native"/>
</id>
<property name="Name"/>
<many-to-one name="leftHand" class="Hand" column="Left_Hand_ID" cascade="all" access="field"/>
<many-to-one name="rightHand" class="Hand" column="Right_Hand_ID" cascade="all" access="field"/>
</class>
<class name="Hand">
<id name="id" type="long" access="field">
<generator class="native"/>
</id>
<property name="Name"/>
<list name="marbles" cascade="all-delete-orphan" access="field">
<key column="Hand_ID"/>
<list-index column="List_Position"/>
<one-to-many class="Marble"/>
</list>
</class>
<class name="Marble">
<id name="id" type="long" access="field">
<generator class="native"/>
</id>
<property name="Name"/>
</class>
</hibernate-mapping>