I seem to be unable to use Lifecycle to implement a custom lifecycle for some of my application entities. I am hoping there is an easy pattern that I am simply ignorant of, but right now I can't see it.
In our production system we have a Campaign object. This Campaign object may have multiple outstanding WorkRequests. Each WorkRequest references some set of initial Jobs, and each Job may have one or more successor Jobs. So the basic structure is:
Campaign <1 --- N> WorkRequest <1 -- N> Job <N -- N> Job
Campaign and WorkRequest have a simple parent-child lifecycle relationship (which all works fine). However, since the Job-to-Job relationships are many-to-many, Jobs do NOT have a simple parent-child lifecycle relationship to each other.
I wanted to implement a Lifecycle.onDelete method on WorkRequest such that whenever it was deleted (directly or via cascade), it would delete all dependent Jobs before it itself was deleted. This is exactly (I think) what Lifecycle is intended for. However, I am running into some serious problems.
I have translated the pattern into a simple example which I am posting here. First, I simplified the naming. Now there are Owners, Roots, and Nodes:
Owner <1 -- N> Root <1 -- N> Node <N -- N> Node
When I delete an Owner, I want that to cascade to the Root, which has a Lifecycle.onDelete method that deletes all the Nodes before the Root itself is deleted.
There are two problems:
- If the Root.onDelete method does not flush, then the Nodes are not deleted before the Root, and when the session *is* finally flushed the Root delete causes a foreign key constraint violation.
- If the Root.onDelete method *does* flush, then if the Root's delete is due to a cascade, we get "Flush during cascade is dangerous"!
In other words, it's a real rock-and-a-hard-place dilemma.
Here are the mappings:
Code:
<class name="Owner">
<id name="id" type="long" unsaved-value="null" >
<meta attribute="scope-set">private</meta>
<generator class="native"/>
</id>
<!-- These are the nodes which are rooted at this Root. -->
<set name="roots" cascade="all-delete-orphan" lazy="true" inverse="true">
<key column="owner_id"/>
<one-to-many class="Root"/>
</set>
</class>
<class name="Root">
<id name="id" type="long" unsaved-value="null" >
<meta attribute="scope-set">private</meta>
<generator class="native"/>
</id>
<many-to-one name="owner" class="Owner" column="owner_id"/>
<!-- These are the nodes which are rooted at this Root. -->
<set name="initialNodes" cascade="save-update" lazy="true">
<key column="initial_root_id"/>
<one-to-many class="Node"/>
</set>
</class>
<!-- Nodes form a graph; each Node has predecessor and successor Nodes.
Each Node has only one Root which determines its lifecycle. -->
<class name="Node">
<id name="id" type="long" unsaved-value="null" >
<meta attribute="scope-set">private</meta>
<generator class="native"/>
</id>
<many-to-one name="root" column="root_id"
class="Root"/>
<set name="successorNodes" table="pred_node_to_succ_node" cascade="save-update">
<key column="successor_node_id"/>
<many-to-many class="Node" column="predecessor_node_id"/>
</set>
<!-- what are this job's predecessor jobs? -->
<set name="predecessorNodes" table="pred_node_to_succ_node" inverse="true">
<key column="predecessor_node_id"/>
<many-to-many class="Node" column="successor_node_id"/>
</set>
</class>
Here are the beans:
Code:
/**
* An Owner holds onto a Root.
*/
public class Owner {
public Owner () { roots = new HashSet(); }
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
private Long id;
public Set getRoots() {
return roots;
}
public void setRoots(Set roots) {
this.roots = roots;
}
private Set roots;
}
/**
* A Root points at a set of initial Nodes in a graph.
*/
public class Root implements Lifecycle {
/** full constructor */
public Root(Owner owner) {
this.owner = owner;
owner.getRoots().add(this);
this.initialNodes = new HashSet();
}
/** default constructor */
public Root() {
}
public Owner getOwner() {
return owner;
}
public void setOwner(Owner owner) {
this.owner = owner;
}
private Owner owner;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
private Long id;
public Set getInitialNodes() {
return initialNodes;
}
public void setInitialNodes(Set initialNodes) {
this.initialNodes = initialNodes;
}
private Set initialNodes;
/**
* Add this node as one of this Root's initial nodes.
*/
public void addInitialNode (Node initialJob) {
this.getInitialNodes().add(initialJob);
}
public boolean onSave(Session session) throws CallbackException {
return false;
}
public boolean onUpdate(Session session) throws CallbackException {
return false;
}
public boolean onDelete(Session session) throws CallbackException {
try {
List list = Persistence.session().find("from Node node where node.root = ?",
this, Hibernate.entity(Root.class));
// now do the deletion
for (int i = 0; i < list.size(); i++) {
Node node = (Node) list.get(i);
Persistence.delete(node);
}
// If we don't flush here, then the nodes are not deleted before the Root,
// and we get constraint violations.
// If we DO flush here, then we are flushing in the middle of a cascade,
// and we get "Flush during cascade is dangerous"!!!
// WHAT TO DO?!?!?!?!
Persistence.flush();
} catch (HibernateException e) {
throw new CallbackException("Couldn't delete dependent nodes", e);
} catch (NfPersistenceException e) {
throw new CallbackException("Couldn't delete dependent nodes", e);
}
return false;
}
public void onLoad(Session session, Serializable serializable) {
}
}
/**
* A Node is an element in a graph.
* Each Node belongs to only one Root, and Nodes are
* deleted by their containing Root when their Root is deleted.
*/
public class Node {
/** full constructor */
public Node(Root root) {
this.root = root;
this.successorNodes = new HashSet();
this.predecessorNodes = new HashSet();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
private Long id;
private Root root;
private Set successorNodes;
private Set predecessorNodes;
public Root getRoot() {
return root;
}
public void setRoot(Root root) {
this.root = root;
}
public Set getSuccessorNodes() {
return successorNodes;
}
public void setSuccessorNodes(Set successorNodes) {
this.successorNodes = successorNodes;
}
public Set getPredecessorNodes() {
return predecessorNodes;
}
public void setPredecessorNodes(Set predecessorNodes) {
this.predecessorNodes = predecessorNodes;
}
/** default constructor */
public Node() {
}
/**
* Add otherNode as a successor of this node.
*/
public void addSuccessor (Node otherJob) {
this.getSuccessorNodes().add(otherJob);
otherJob.getPredecessorNodes().add(this);
}
}
Here is the test code (please disregard my "Persistence" static class wrapper):
Code:
public class Main {
public static void main (String[] argv) throws Exception {
Persistence.initDirectJdbc();
Persistence.dropDatabase();
Persistence.createDatabase();
Session session = Persistence.session();
Transaction tx = session.beginTransaction();
// Create an Owner
Owner owner = new Owner();
session.save(owner);
// Create a Root
Root root = new Root(owner);
Node node = new Node(root);
root.addInitialNode(node);
Node successor = new Node(root);
node.addSuccessor(successor);
// flush will save everything by cascade from the Owner
session.flush();
tx.commit();
Persistence.closeSession();
// Now try deleting
session = Persistence.session(); // get another thread-local session
tx = session.beginTransaction();
List list = session.find("from Owner");
owner = (Owner)list.get(0);
// This should trigger the cascading delete from Owner on down to Root,
// then into Root's Lifecycle.onDelete to delete all the dependent Nodes
// BEFORE deleting the Root.
session.delete(owner);
// And this should flush the cascading deletes.
session.flush();
tx.commit();
session.close();
}
}
And here is the Hibernate debug output, starting just after the "session.find":
Code:
11:41:25,266 DEBUG SessionImpl:1460 - find: from Owner
11:41:25,297 DEBUG QueryTranslator:147 - compiling query
11:41:25,313 DEBUG SessionImpl:2193 - flushing session
11:41:25,313 DEBUG SessionImpl:2321 - Flushing entities and processing referenced collections
11:41:25,313 DEBUG SessionImpl:2664 - Processing unreferenced collections
11:41:25,328 DEBUG SessionImpl:2678 - Scheduling collection removes/(re)creates/updates
11:41:25,328 DEBUG SessionImpl:2217 - Flushed: 0 insertions, 0 updates, 0 deletions to 0 objects
11:41:25,328 DEBUG SessionImpl:2222 - Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
11:41:25,328 DEBUG SessionImpl:1745 - Dont need to execute flush
11:41:25,328 DEBUG QueryTranslator:199 - HQL: from Owner
11:41:25,328 DEBUG QueryTranslator:200 - SQL: select owner0_.id as id from Owner owner0_
11:41:25,328 DEBUG BatcherImpl:192 - about to open: 0 open PreparedStatements, 0 open ResultSets
11:41:25,328 DEBUG SQL:223 - select owner0_.id as id from Owner owner0_
11:41:25,328 DEBUG BatcherImpl:227 - preparing statement
11:41:25,328 DEBUG Loader:196 - processing result set
11:41:25,344 DEBUG LongType:68 - returning '1' as column: id
11:41:25,344 DEBUG Loader:404 - result row: 1
11:41:25,344 DEBUG Loader:535 - Initializing object from ResultSet: 1
11:41:25,344 DEBUG Loader:604 - Hydrating entity: Owner#1
11:41:25,344 DEBUG Loader:225 - done processing result set (1 rows)
11:41:25,344 DEBUG BatcherImpl:199 - done closing: 0 open PreparedStatements, 0 open ResultSets
11:41:25,359 DEBUG BatcherImpl:240 - closing statement
11:41:25,359 DEBUG Loader:238 - total objects hydrated: 1
11:41:25,359 DEBUG SessionImpl:2129 - resolving associations for [Owner#1]
11:41:25,359 DEBUG SessionImpl:3746 - collection not cached
11:41:25,359 DEBUG SessionImpl:2153 - done materializing entity [Owner#1]
11:41:25,359 DEBUG SessionImpl:3000 - initializing non-lazy collections
11:41:25,359 DEBUG SessionImpl:1099 - deleting a persistent instance
11:41:25,359 DEBUG SessionImpl:1119 - deleting [Owner#1]
11:41:25,359 DEBUG Cascades:497 - processing cascades for: Owner
11:41:25,359 DEBUG Cascades:524 - cascading to collection: Owner.roots
11:41:25,359 DEBUG SessionImpl:3132 - initializing collection [Owner.roots#1]
11:41:25,359 DEBUG BatcherImpl:192 - about to open: 0 open PreparedStatements, 0 open ResultSets
11:41:25,375 DEBUG SQL:223 - select roots0_.id as id__, roots0_.owner_id as owner_id__, roots0_.id as id0_, roots0_.owner_id as owner_id0_ from Root roots0_ where roots0_.owner_id=?
11:41:25,375 DEBUG BatcherImpl:227 - preparing statement
11:41:25,375 DEBUG LongType:46 - binding '1' to parameter: 1
11:41:25,375 DEBUG Loader:326 - result set contains (possibly empty) collection: [Owner.roots#1]
11:41:25,375 DEBUG SessionImpl:2902 - uninitialized collection: initializing
11:41:25,375 DEBUG Loader:196 - processing result set
11:41:25,375 DEBUG LongType:68 - returning '1' as column: id0_
11:41:25,375 DEBUG Loader:404 - result row: 1
11:41:25,375 DEBUG Loader:535 - Initializing object from ResultSet: 1
11:41:25,375 DEBUG Loader:604 - Hydrating entity: Root#1
11:41:25,375 DEBUG LongType:68 - returning '1' as column: owner_id0_
11:41:25,391 DEBUG LongType:68 - returning '1' as column: owner_id__
11:41:25,391 DEBUG Loader:288 - found row of collection: [Owner.roots#1]
11:41:25,391 DEBUG SessionImpl:2925 - reading row
11:41:25,391 DEBUG LongType:68 - returning '1' as column: id__
11:41:25,391 DEBUG SessionImpl:1913 - loading [Root#1]
11:41:25,391 DEBUG SessionImpl:2010 - attempting to resolve [Root#1]
11:41:25,391 DEBUG SessionImpl:2026 - resolved object in session cache [Root#1]
11:41:25,391 DEBUG Loader:225 - done processing result set (1 rows)
11:41:25,391 DEBUG BatcherImpl:199 - done closing: 0 open PreparedStatements, 0 open ResultSets
11:41:25,406 DEBUG BatcherImpl:240 - closing statement
11:41:25,406 DEBUG Loader:238 - total objects hydrated: 1
11:41:25,406 DEBUG SessionImpl:2129 - resolving associations for [Root#1]
11:41:25,406 DEBUG SessionImpl:1913 - loading [Owner#1]
11:41:25,406 DEBUG SessionImpl:2010 - attempting to resolve [Owner#1]
11:41:25,406 DEBUG SessionImpl:2026 - resolved object in session cache [Owner#1]
11:41:25,406 DEBUG SessionImpl:3746 - collection not cached
11:41:25,406 DEBUG SessionImpl:2145 - calling onLoad()
11:41:25,406 DEBUG SessionImpl:2153 - done materializing entity [Root#1]
11:41:25,406 DEBUG SessionImpl:2961 - 1 collections were found in result set
11:41:25,406 DEBUG SessionImpl:2979 - collection fully initialized: [Owner.roots#1]
11:41:25,406 DEBUG SessionImpl:2982 - 1 collections initialized
11:41:25,422 DEBUG SessionImpl:3000 - initializing non-lazy collections
11:41:25,422 DEBUG Cascades:60 - cascading to delete()
11:41:25,422 DEBUG SessionImpl:1099 - deleting a persistent instance
11:41:25,422 DEBUG SessionImpl:1119 - deleting [Root#1]
11:41:25,422 DEBUG SessionImpl:1146 - calling onDelete()
11:41:27,797 DEBUG SessionImpl:1460 - find: from Node node where node.root = ?
11:41:27,797 DEBUG QueryParameters:105 - parameters: [Root#1]
11:41:27,797 DEBUG QueryTranslator:147 - compiling query
11:41:27,813 DEBUG SessionImpl:2193 - flushing session
11:41:27,828 DEBUG SessionImpl:2321 - Flushing entities and processing referenced collections
11:41:27,828 DEBUG SessionImpl:2664 - Processing unreferenced collections
11:41:27,828 DEBUG SessionImpl:2785 - Collection dereferenced: [Owner.roots#1]
11:41:27,828 DEBUG SessionImpl:2785 - Collection dereferenced: [Root.initialNodes#1]
11:41:27,828 DEBUG SessionImpl:2678 - Scheduling collection removes/(re)creates/updates
11:41:27,828 DEBUG SessionImpl:2217 - Flushed: 0 insertions, 0 updates, 0 deletions to 2 objects
11:41:27,828 DEBUG SessionImpl:2222 - Flushed: 0 (re)creations, 0 updates, 2 removals to 2 collections
11:41:27,844 DEBUG Printer:75 - listing entities:
11:41:27,844 DEBUG Printer:82 - Root{initialNodes=uninitialized, owner=Owner#1, id=1}
11:41:27,844 DEBUG Printer:82 - Owner{roots=[Root#1], id=1}
11:41:27,844 DEBUG SessionImpl:2247 - changes must be flushed to space: Node
11:41:27,844 DEBUG SessionImpl:1736 - Need to execute flush
11:41:27,844 DEBUG SessionImpl:2258 - executing flush
11:41:27,844 DEBUG BasicCollectionPersister:491 - Deleting collection: [Root.initialNodes#1]
11:41:27,844 DEBUG BatcherImpl:192 - about to open: 0 open PreparedStatements, 0 open ResultSets
11:41:27,844 DEBUG SQL:223 - update Node set initial_root_id=null where initial_root_id=?
11:41:27,844 DEBUG BatcherImpl:227 - preparing statement
11:41:27,859 DEBUG LongType:46 - binding '1' to parameter: 1
11:41:27,859 DEBUG BasicCollectionPersister:507 - done deleting collection
11:41:27,859 DEBUG BatcherImpl:199 - done closing: 0 open PreparedStatements, 0 open ResultSets
11:41:27,859 DEBUG BatcherImpl:240 - closing statement
11:41:27,859 DEBUG SessionImpl:2708 - post flush
11:41:27,859 DEBUG QueryTranslator:199 - HQL: from Node node where node.root = ?
11:41:27,859 DEBUG QueryTranslator:200 - SQL: select node0_.id as id, node0_.root_id as root_id from Node node0_ where (node0_.root_id=? )
11:41:27,859 DEBUG BatcherImpl:192 - about to open: 0 open PreparedStatements, 0 open ResultSets
11:41:27,859 DEBUG SQL:223 - select node0_.id as id, node0_.root_id as root_id from Node node0_ where (node0_.root_id=? )
11:41:27,859 DEBUG BatcherImpl:227 - preparing statement
11:41:27,859 DEBUG LongType:46 - binding '1' to parameter: 1
11:41:27,859 DEBUG Loader:196 - processing result set
11:41:27,875 DEBUG LongType:68 - returning '1' as column: id
11:41:27,875 DEBUG Loader:404 - result row: 1
11:41:27,875 DEBUG Loader:535 - Initializing object from ResultSet: 1
11:41:27,875 DEBUG Loader:604 - Hydrating entity: Node#1
11:41:27,875 DEBUG LongType:68 - returning '1' as column: root_id
11:41:27,875 DEBUG LongType:68 - returning '2' as column: id
11:41:27,875 DEBUG Loader:404 - result row: 2
11:41:27,875 DEBUG Loader:535 - Initializing object from ResultSet: 2
11:41:27,875 DEBUG Loader:604 - Hydrating entity: Node#2
11:41:27,875 DEBUG LongType:68 - returning '1' as column: root_id
11:41:27,875 DEBUG Loader:225 - done processing result set (2 rows)
11:41:27,891 DEBUG BatcherImpl:199 - done closing: 0 open PreparedStatements, 0 open ResultSets
11:41:27,891 DEBUG BatcherImpl:240 - closing statement
11:41:27,891 DEBUG Loader:238 - total objects hydrated: 2
11:41:27,891 DEBUG SessionImpl:2129 - resolving associations for [Node#1]
11:41:27,891 DEBUG SessionImpl:1913 - loading [Root#1]
11:41:27,891 DEBUG SessionImpl:2010 - attempting to resolve [Root#1]
11:41:27,891 DEBUG SessionImpl:2026 - resolved object in session cache [Root#1]
11:41:27,891 DEBUG SessionImpl:3746 - collection not cached
11:41:27,891 DEBUG SessionImpl:3746 - collection not cached
11:41:27,891 DEBUG SessionImpl:2153 - done materializing entity [Node#1]
11:41:27,906 DEBUG SessionImpl:2129 - resolving associations for [Node#2]
11:41:27,906 DEBUG SessionImpl:1913 - loading [Root#1]
11:41:27,906 DEBUG SessionImpl:2010 - attempting to resolve [Root#1]
11:41:27,906 DEBUG SessionImpl:2026 - resolved object in session cache [Root#1]
11:41:27,906 DEBUG SessionImpl:3746 - collection not cached
11:41:27,906 DEBUG SessionImpl:3746 - collection not cached
11:41:27,906 DEBUG SessionImpl:2153 - done materializing entity [Node#2]
11:41:27,906 DEBUG SessionImpl:3000 - initializing non-lazy collections
11:41:27,906 DEBUG SessionImpl:3132 - initializing collection [Node.predecessorNodes#2]
11:41:27,906 DEBUG BatcherImpl:192 - about to open: 0 open PreparedStatements, 0 open ResultSets
11:41:27,906 DEBUG SQL:223 - select predecesso0_.successor_node_id as successo1___, predecesso0_.predecessor_node_id as predeces2___, node1_.id as id0_, node1_.root_id as root_id0_, root2_.id as id1_, root2_.owner_id as owner_id1_, owner3_.id as id2_ from pred_node_to_succ_node predecesso0_ inner join Node node1_ on predecesso0_.successor_node_id=node1_.id left outer join Root root2_ on node1_.root_id=root2_.id left outer join Owner owner3_ on root2_.owner_id=owner3_.id where predecesso0_.predecessor_node_id=?
11:41:27,922 DEBUG BatcherImpl:227 - preparing statement
11:41:27,922 DEBUG LongType:46 - binding '2' to parameter: 1
11:41:27,938 DEBUG Loader:326 - result set contains (possibly empty) collection: [Node.predecessorNodes#2]
11:41:27,938 DEBUG SessionImpl:2902 - uninitialized collection: initializing
11:41:27,938 DEBUG Loader:196 - processing result set
11:41:27,938 DEBUG LongType:68 - returning '1' as column: id0_
11:41:27,938 DEBUG LongType:68 - returning '1' as column: id1_
11:41:27,938 DEBUG LongType:68 - returning '1' as column: id2_
11:41:27,938 DEBUG Loader:404 - result row: 1, 1, 1
11:41:27,938 DEBUG LongType:68 - returning '2' as column: predeces2___
11:41:27,938 DEBUG Loader:288 - found row of collection: [Node.predecessorNodes#2]
11:41:27,938 DEBUG SessionImpl:2925 - reading row
11:41:27,938 DEBUG LongType:68 - returning '1' as column: successo1___
11:41:27,953 DEBUG SessionImpl:1913 - loading [Node#1]
11:41:27,953 DEBUG SessionImpl:2010 - attempting to resolve [Node#1]
11:41:27,953 DEBUG SessionImpl:2026 - resolved object in session cache [Node#1]
11:41:27,953 DEBUG Loader:225 - done processing result set (1 rows)
11:41:27,953 DEBUG BatcherImpl:199 - done closing: 0 open PreparedStatements, 0 open ResultSets
11:41:27,953 DEBUG BatcherImpl:240 - closing statement
11:41:27,953 DEBUG Loader:238 - total objects hydrated: 0
11:41:27,953 DEBUG SessionImpl:2961 - 1 collections were found in result set
11:41:27,953 DEBUG SessionImpl:2979 - collection fully initialized: [Node.predecessorNodes#2]
11:41:27,953 DEBUG SessionImpl:2982 - 1 collections initialized
11:41:27,953 DEBUG SessionImpl:3132 - initializing collection [Node.successorNodes#2]
11:41:27,953 DEBUG BatcherImpl:192 - about to open: 0 open PreparedStatements, 0 open ResultSets
11:41:27,953 DEBUG SQL:223 - select successorn0_.predecessor_node_id as predeces2___, successorn0_.successor_node_id as successo1___, node1_.id as id0_, node1_.root_id as root_id0_, root2_.id as id1_, root2_.owner_id as owner_id1_, owner3_.id as id2_ from pred_node_to_succ_node successorn0_ inner join Node node1_ on successorn0_.predecessor_node_id=node1_.id left outer join Root root2_ on node1_.root_id=root2_.id left outer join Owner owner3_ on root2_.owner_id=owner3_.id where successorn0_.successor_node_id=?
11:41:27,969 DEBUG BatcherImpl:227 - preparing statement
11:41:27,969 DEBUG LongType:46 - binding '2' to parameter: 1
11:41:27,969 DEBUG Loader:326 - result set contains (possibly empty) collection: [Node.successorNodes#2]
11:41:27,969 DEBUG SessionImpl:2902 - uninitialized collection: initializing
11:41:27,969 DEBUG Loader:196 - processing result set
11:41:27,984 DEBUG Loader:225 - done processing result set (0 rows)
11:41:27,984 DEBUG BatcherImpl:199 - done closing: 0 open PreparedStatements, 0 open ResultSets
11:41:27,984 DEBUG BatcherImpl:240 - closing statement
11:41:27,984 DEBUG Loader:238 - total objects hydrated: 0
11:41:27,984 DEBUG SessionImpl:2961 - 1 collections were found in result set
11:41:27,984 DEBUG SessionImpl:2979 - collection fully initialized: [Node.successorNodes#2]
11:41:27,984 DEBUG SessionImpl:2982 - 1 collections initialized
11:41:27,984 DEBUG SessionImpl:3132 - initializing collection [Node.predecessorNodes#1]
11:41:27,984 DEBUG BatcherImpl:192 - about to open: 0 open PreparedStatements, 0 open ResultSets
11:41:27,984 DEBUG SQL:223 - select predecesso0_.successor_node_id as successo1___, predecesso0_.predecessor_node_id as predeces2___, node1_.id as id0_, node1_.root_id as root_id0_, root2_.id as id1_, root2_.owner_id as owner_id1_, owner3_.id as id2_ from pred_node_to_succ_node predecesso0_ inner join Node node1_ on predecesso0_.successor_node_id=node1_.id left outer join Root root2_ on node1_.root_id=root2_.id left outer join Owner owner3_ on root2_.owner_id=owner3_.id where predecesso0_.predecessor_node_id=?
11:41:27,984 DEBUG BatcherImpl:227 - preparing statement
11:41:28,000 DEBUG LongType:46 - binding '1' to parameter: 1
11:41:28,000 DEBUG Loader:326 - result set contains (possibly empty) collection: [Node.predecessorNodes#1]
11:41:28,000 DEBUG SessionImpl:2902 - uninitialized collection: initializing
11:41:28,000 DEBUG Loader:196 - processing result set
11:41:28,000 DEBUG Loader:225 - done processing result set (0 rows)
11:41:28,000 DEBUG BatcherImpl:199 - done closing: 0 open PreparedStatements, 0 open ResultSets
11:41:28,000 DEBUG BatcherImpl:240 - closing statement
11:41:28,000 DEBUG Loader:238 - total objects hydrated: 0
11:41:28,000 DEBUG SessionImpl:2961 - 1 collections were found in result set
11:41:28,000 DEBUG SessionImpl:2979 - collection fully initialized: [Node.predecessorNodes#1]
11:41:28,000 DEBUG SessionImpl:2982 - 1 collections initialized
11:41:28,016 DEBUG SessionImpl:3132 - initializing collection [Node.successorNodes#1]
11:41:28,016 DEBUG BatcherImpl:192 - about to open: 0 open PreparedStatements, 0 open ResultSets
11:41:28,016 DEBUG SQL:223 - select successorn0_.predecessor_node_id as predeces2___, successorn0_.successor_node_id as successo1___, node1_.id as id0_, node1_.root_id as root_id0_, root2_.id as id1_, root2_.owner_id as owner_id1_, owner3_.id as id2_ from pred_node_to_succ_node successorn0_ inner join Node node1_ on successorn0_.predecessor_node_id=node1_.id left outer join Root root2_ on node1_.root_id=root2_.id left outer join Owner owner3_ on root2_.owner_id=owner3_.id where successorn0_.successor_node_id=?
11:41:28,016 DEBUG BatcherImpl:227 - preparing statement
11:41:28,016 DEBUG LongType:46 - binding '1' to parameter: 1
11:41:28,016 DEBUG Loader:326 - result set contains (possibly empty) collection: [Node.successorNodes#1]
11:41:28,016 DEBUG SessionImpl:2902 - uninitialized collection: initializing
11:41:28,016 DEBUG Loader:196 - processing result set
11:41:28,031 DEBUG LongType:68 - returning '2' as column: id0_
11:41:28,031 DEBUG LongType:68 - returning '1' as column: id1_
11:41:28,031 DEBUG LongType:68 - returning '1' as column: id2_
11:41:28,031 DEBUG Loader:404 - result row: 2, 1, 1
11:41:28,031 DEBUG LongType:68 - returning '1' as column: successo1___
11:41:28,031 DEBUG Loader:288 - found row of collection: [Node.successorNodes#1]
11:41:28,031 DEBUG SessionImpl:2925 - reading row
11:41:28,031 DEBUG LongType:68 - returning '2' as column: predeces2___
11:41:28,031 DEBUG SessionImpl:1913 - loading [Node#2]
11:41:28,031 DEBUG SessionImpl:2010 - attempting to resolve [Node#2]
11:41:28,031 DEBUG SessionImpl:2026 - resolved object in session cache [Node#2]
11:41:28,031 DEBUG Loader:225 - done processing result set (1 rows)
11:41:28,031 DEBUG BatcherImpl:199 - done closing: 0 open PreparedStatements, 0 open ResultSets
11:41:28,047 DEBUG BatcherImpl:240 - closing statement
11:41:28,047 DEBUG Loader:238 - total objects hydrated: 0
11:41:28,047 DEBUG SessionImpl:2961 - 1 collections were found in result set
11:41:28,047 DEBUG SessionImpl:2979 - collection fully initialized: [Node.successorNodes#1]
11:41:28,047 DEBUG SessionImpl:2982 - 1 collections initialized
11:41:28,047 DEBUG SessionImpl:1099 - deleting a persistent instance
11:41:28,047 DEBUG SessionImpl:1119 - deleting [Node#1]
11:41:28,047 DEBUG Cascades:497 - processing cascades for: Node
11:41:28,047 DEBUG Cascades:506 - done processing cascades for: Node
11:41:28,047 DEBUG Cascades:497 - processing cascades for: Node
11:41:28,063 DEBUG Cascades:506 - done processing cascades for: Node
11:41:28,063 DEBUG SessionImpl:1099 - deleting a persistent instance
11:41:28,063 DEBUG SessionImpl:1119 - deleting [Node#2]
11:41:28,063 DEBUG Cascades:497 - processing cascades for: Node
11:41:28,063 DEBUG Cascades:506 - done processing cascades for: Node
11:41:28,063 DEBUG Cascades:497 - processing cascades for: Node
11:41:28,063 DEBUG Cascades:506 - done processing cascades for: Node
net.sf.hibernate.CallbackException: Couldn't delete dependent jobs
at Root.onDelete(Root.java:88)
at net.sf.hibernate.impl.SessionImpl.doDelete(SessionImpl.java:1147)
at net.sf.hibernate.impl.SessionImpl.delete(SessionImpl.java:1113)
at net.sf.hibernate.engine.Cascades$1.cascade(Cascades.java:61)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:436)
at net.sf.hibernate.engine.Cascades.cascadeCollection(Cascades.java:526)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:452)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:503)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:482)
at net.sf.hibernate.impl.SessionImpl.doDelete(SessionImpl.java:1174)
at net.sf.hibernate.impl.SessionImpl.delete(SessionImpl.java:1113)
at Main.main(Main.java:50)
Caused by: NfPersistenceException: Could not flush
at Persistence.flush(Persistence.java:252)
at Root.onDelete(Root.java:84)
... 11 more
Caused by: net.sf.hibernate.HibernateException: Flush during cascade is dangerous - this might occur if an object was deleted and then re-saved by cascade (remove deleted object from associations)
at net.sf.hibernate.impl.SessionImpl.flush(SessionImpl.java:2181)
at Persistence.flush(Persistence.java:250)
... 12 more
So what can be done here? It definitely seems as though Lifecycle is intended to allow application-specific cascade behaviors, including cascaded deletes. But if the only way to implement those behaviors is by forcing a flush of dependent object deletes, and if there is already a cascade happening, then Lifecycle seems unable to do its job. (Commenting out the flush() call in Root.onDelete() causes the foreign key constraint violation, which I can post on request -- this posting is too long already!)
Is there a better pattern here? In this particular case I know I could just do a straight parent-child relationship from Root to Node, but there are other cases in our app where this would not be feasible.
Cheers,
Rob