My first project with Hibernate/JPA and Play!, I’ve got a menu that once working, will support changes (i.e. easily add nodes to the tree). Struggling (in the >5 hours sense) just to get the basic modelling together:
The model:
@Entity
@Table(name = "Node")
public class Node extends Model {
@Column(nullable=false)
public String description;
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="parent")
public List<Node> children = new LinkedList<Node>();
@ManyToOne
@JoinColumn(name="parent", insertable=false, updatable=false)
public Node parent;
public Node(){}
}
The util class:
public class NodeUtil {
public static void addChild(Node root, String description) {
Node child = new Node();
child.description = description;
child.parent = root;
root.children.add(child);
root.save();
}
private static final String MENU_NAME = "MainMenu";
public static Node getMenu() {
return getRoot(MENU_NAME);
}
public static Node getRoot(String name) {
Node root = Node.find("byDescription", name).first();
if (root == null) {
root = new Node();
root.description = name;
root.save();
}
return root;
}
}
The test:
public class MenuTest extends UnitTest {
private static final String TEST_MENU = "testMenu";
@Test
public void testMenu() {
// test build/get
Node root = NodeUtil.getRoot(TEST_MENU);
assertNotNull(root);
// delete all children - maybe from previous tests etc.
for(Node o : root.children)
o.delete();
root.save();
// test add
root = NodeUtil.getRoot(TEST_MENU);
NodeUtil.addChild(root, "child 1");
NodeUtil.addChild(root, "child 2");
NodeUtil.addChild(root, "child 3");
assertEquals(3, root.children.size());
assertEquals("child 3", root.children.get(2).description);
assertEquals(0, root.children.get(1).children.size());
Node node = root.children.get(1);
NodeUtil.addChild(node, "subchild 1");
NodeUtil.addChild(node, "subchild 2");
NodeUtil.addChild(node, "subchild 3");
NodeUtil.addChild(node, "subchild 4");
NodeUtil.addChild(root.children.get(2), "sub subchild");
assertEquals("sub subchild", root
.children.get(1)
.children.get(2)
.children.get(0)
.description);
assertEquals(4, root.children.get(1).children.size());
root.save();
// test delete
root = NodeUtil.getRoot(TEST_MENU); // regrab the root via hibernate, assuming there isnt it isnt cached this will get changes that have been persisted to the db (maybe?)
root.children.get(1).children.get(2).delete();
assertEquals(3, root.children.get(1).children.size());
//root.delete();
}
}
Questions:
-
What am I doing wrong? (I.e. I just can’t get this simple idea to be modelled and to pass the unit test. Like I said, new to Hibernate, and every change I make yields a new Hibernate error variant, which means nothing to me. E.g. this current setup throws “detached entity passed to persist: models.Node”)
-
Initially I had the entire util class as a bunch of static methods in the model class. Firstly, do static methods affect Hibernates modelling? If so, in brief, under what circumstances can I have static methods (and members, come to think of it) that will be “transient” to the object modelling?
-
Assuming that I keep the util methods in a separate public util class, where is this class conventionally stored in the play framework? At the moment it’s in the models package, next to the Node.java.
I’m not familiar with Play Framework, but I can make some point regarding working with Hibernate:
Maintaining consistent state of the objects in memory is your responsibility. Consider the following code:
You instructed Hibernate to delete children from the database, but the
rootobject in memory still references them. Since the relationship is configured with cascading, Hibernate will try to save them again (I guess it’s the reason of “detached entity passed to persist” error). So, keep in-memory state of the object consistent by clearingroot.children.Hibernate heavily relies on the concept of Unit of Work. I’m not sure how Play manages it, but it looks like you should call
clearJPASession()in unittests to make sure that exisiting session state wouldn’t affect further operations:The way you defined a relationship is supported, but not recommended (see 2.2.5.3.1.1. Bidirectional). Use the following approach instead:
Static methods doesn’t interfere with Hibernate.