Igor Kromin |   Consultant. Coder. Blogger. Tinkerer. Gamer.

| Views: 6809
After searching high and low to find out how to add a child object to a parent in JDO efficiently and getting ever more frustrated with the lack of good documentation, I've decided to just write some of my own tests to see what's possible and how well it works.

Of course there are articles on how to do this in JDO, but even the author, who is one of the engineers working on GAE fails to answer questions like:
One question about performance and efficiency here... To persist a new Chapter object we need to retrieve the whole Book object... for me this looks like not the most efficient way to do especially if your book object is huge.
Is there a way to just save the Chapter like I would do using a simple JDBC call with (insert into...)? Knowing that the Chapter as a reference on Book and so has its Id, this should be something possible but I cannot find a way to do it.


So, I've defined two objects, ParentObject and a ChildObject with a one-to-many owned relationship between the parent and the child i.e. the parent can have many children.

The tests I ran were:
  • Adding a child to the parent before persist
  • Setting the parent object on the child and persisting both parent and child together
  • Timing how long it takes to add one child to a parent with 0 children
  • Timing how long it takes to add one child to a parent with 10000 children


The results were quite surprising to me, but the answer to my original question was also clear - you can't do this very well in JDO, so I'm taking all JDO code out of my game server and moving to something else.


So first thing's first, these are the listings for the two objects I was testing with.
package com.colorsin;
import java.util.List;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
@PersistenceCapable
public class ParentObject {
@PrimaryKey
@Persistent
private Key id;
@Persistent(mappedBy = "parent")
private List<ChildObject> children;
public ParentObject() {
}
public ParentObject(String id) {
setId(id);
}
public void setChildren(List<ChildObject> list) {
this.children = list;
}
public String getId() {
return id.getName();
}
public void setId(String id) {
this.id = KeyFactory.createKey(ParentObject.class.getSimpleName(), id);
}
public List<ChildObject> getChildren() {
return children;
}
}

package com.colorsin;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;
@PersistenceCapable
public class ChildObject {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key id;
@Persistent(mappedBy = "children")
ParentObject parent;
public ChildObject() {
}
public void setParent(ParentObject p) {
this.parent = p;
}
}


Fairly straight forward, the parent object has a Key that we generate based on the ID (name) that's passed in. The child object has an auto-generated Key.

Now for the tests...the DataStoreHelper.pmf() calls simply returns an instance of PersistenceManagerFactory.
@Test
public void jdoChildPersist1() {
PersistenceManagerFactory pmf = DataStoreHelper.pmf();
PersistenceManager pm = pmf.getPersistenceManager();
ChildObject c = new ChildObject();
ParentObject p = new ParentObject("pid1");
p.setChildren(new ArrayList<ChildObject>());
p.getChildren().add(c);
pm.currentTransaction().begin();
pm.makePersistent(p);
pm.currentTransaction().commit();
p = pm.getObjectById(ParentObject.class, "pid1");
assertNotNull(p.getChildren());
assertEquals(1, p.getChildren().size()); // <-- passes
}


Here I create a child and parent, then create a list of children on the parent with the child I've just created. Everything works as expected.

Next test...
@Test
public void jdoChildPersist2() {
PersistenceManagerFactory pmf = DataStoreHelper.pmf();
PersistenceManager pm = pmf.getPersistenceManager();
ParentObject p = new ParentObject("pid1");
ChildObject c = new ChildObject();
c.setParent(p);
pm.currentTransaction().begin();
pm.makePersistent(p);
pm.currentTransaction().commit();
p = pm.getObjectById(ParentObject.class, "pid1");
assertNotNull(p.getChildren());
assertEquals(1, p.getChildren().size()); // <-- fails
}


Here's where it gets interesting, I create a parent and a child and simply set the parent object on the child. There is a bi-directional relationship defined, so surely it will work, but no, it fails. This could be to do with parent and child being in different entity groups, but for the life of me, I could not figure out how to force two objects into the same entity group using JDO.

Now accepting that I can't just set the parent on the child, I decided to see how long it takes to add a new child...
@Test
public void jdoChildPersist3() {
PersistenceManagerFactory pmf = DataStoreHelper.pmf();
PersistenceManager pm = pmf.getPersistenceManager();
ParentObject p = new ParentObject("pid1");
pm.currentTransaction().begin();
pm.makePersistent(p);
pm.currentTransaction().commit();
p = pm.getObjectById(ParentObject.class, "pid1");
System.out.println("Start");
long startTime = System.currentTimeMillis();
pm.currentTransaction().begin();
p.getChildren().add(new ChildObject());
pm.currentTransaction().commit();
long endTime = System.currentTimeMillis() - startTime;
System.out.println("Elapsed time: " + endTime); // <-- 4ms
assertNotNull(p.getChildren());
assertEquals(1, p.getChildren().size()); // <-- passes
}


Here I persist a parent in one transaction, then start a new transaction, get the child list from the parent object and add to that list. This is quite fast, 4ms.

Now what if there were 10000 children on the parent already?
@Test
public void jdoChildPersist4() {
PersistenceManagerFactory pmf = DataStoreHelper.pmf();
PersistenceManager pm = pmf.getPersistenceManager();
ParentObject p = new ParentObject("pid1");
ArrayList<ChildObject> children = new ArrayList<ChildObject>();
for (int i = 0; i < 10000; i++) {
children.add(new ChildObject());
}
p.setChildren(children);
pm.currentTransaction().begin();
pm.makePersistent(p);
pm.currentTransaction().commit();
p = pm.getObjectById(ParentObject.class, "pid1");
System.out.println("Start");
long startTime = System.currentTimeMillis();
pm.currentTransaction().begin();
p.getChildren().add(new ChildObject());
pm.currentTransaction().commit();
long endTime = System.currentTimeMillis() - startTime;
System.out.println("Elapsed time: " + endTime); // <-- 261ms
assertNotNull(p.getChildren());
assertEquals(10001, p.getChildren().size()); // <-- passes
}


Ok, well that's a bummer, 261ms to add just one child! Looks like JDO really does have to fetch all of the child entities on the parent before a new child can be added. This of course is a big deal breaker.

Unowned relationships could solve the performance issue, but I have not found any clear documentation on how to use an unowned relationship between a parent and child and still have them in the same entity group. Maybe it's something simple and I've just missed it, but for now I've decided JDO is not the way to go for my app.

-i

Have comments or feedback on what I wrote? Please share them below! Found this useful? Consider sending me a small tip.
comments powered by Disqus
Other posts you may like...