surinder-insonix / datanucleus-appengine

Automatically exported from code.google.com/p/datanucleus-appengine
0 stars 0 forks source link

Problem with bidirectional, one-to-one relationships during persistence #228

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
Hello everyone!

We're using Groovy 1.7.8 and JDK 1.6.0_22 on AppEngine 1.4.2, and we're running 
into problems with a bidirectional one-to-one relationship.  We can get 
bidirectional one-to-manys working, but one-to-ones seem to fail.

I've attached test code.  It includes three pairs of parent/child objects and 
three JUnit tests that test each pair.  All tests fail identically with: 

jjava.lang.IllegalStateException: Primary key for object of type Child2 is null.
    at org.datanucleus.store.appengine.EntityUtils.getPkAsKey(EntityUtils.java:152)
    at org.datanucleus.store.appengine.DatastoreFieldManager.getKeyForObject(DatastoreFieldManager.java:625)
    at org.datanucleus.store.appengine.DatastoreFieldManager.getParentKeyFromParentField(DatastoreFieldManager.java:634)
    at org.datanucleus.store.appengine.DatastoreFieldManager.establishEntityGroup(DatastoreFieldManager.java:591)
    at org.datanucleus.store.appengine.DatastorePersistenceHandler.insertPreProcess(DatastorePersistenceHandler.java:343)
    at org.datanucleus.store.appengine.DatastorePersistenceHandler.insertObjects(DatastorePersistenceHandler.java:251)
    at org.datanucleus.store.appengine.DatastorePersistenceHandler.insertObject(DatastorePersistenceHandler.java:240)
    at org.datanucleus.state.JDOStateManagerImpl.internalMakePersistent(JDOStateManagerImpl.java:3185)
    at org.datanucleus.state.JDOStateManagerImpl.makePersistent(JDOStateManagerImpl.java:3161)
    at org.datanucleus.ObjectManagerImpl.persistObjectInternal(ObjectManagerImpl.java:1298)
    at org.datanucleus.ObjectManagerImpl.persistObject(ObjectManagerImpl.java:1175)
    at org.datanucleus.jdo.JDOPersistenceManager.jdoMakePersistent(JDOPersistenceManager.java:669)
    at org.datanucleus.jdo.JDOPersistenceManager.makePersistent(JDOPersistenceManager.java:694)
    at javax.jdo.PersistenceManager$makePersistent.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:40)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:124)
    at com.[ourcompany].TestSuite.test2_IDENTITYGeneratedKeysForAll(TestModels.groovy:139) <-- The "makePersistent" line
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

Here is a cut n' paste of the second pair of objects and their test:

@PersistenceCapable
class Parent2 {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    Key id

    @Persistent
    String aString;

    @Persistent(mappedBy = "parent")
    Child2 child

    public void setChild(Child2 c) { child = c; child.parent = this; }
}
@PersistenceCapable
class Child2 {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    Key id

    @Persistent
    Parent2 parent

    @Persistent
    String aString
}

class TestSuite {
    private final LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());

    @Before
    public void setUp() {
        System.setProperty("appengine.orm.disable.duplicate.pmf.exception", "false");
        helper.setUp();
    }
    @After
    public void tearDown() {
        helper.tearDown();
    }

    @Test
    public void test2_IDENTITYGeneratedKeysForAll() {
        PersistenceManagerFactory pmfInstance = JDOHelper.getPersistenceManagerFactory("transactions-optional")
        PersistenceManager pm = pmfInstance.getPersistenceManager()

        Child2 c = new Child2(aString:"Child info")
        Parent2 p = new Parent2(aString:"Not important", child:c)
        assert p.child == c && c.parent == p

        pm.makePersistent(p)
        pm.close()
    }
}

Googling around, I couldn't find anyone else running into this (anything close 
had to do with one-to-manys), which makes me paranoid that I'm doing something 
dumb...  But it looks like a bug.

Thanks!
Jason

Original issue reported on code.google.com by ja...@bobberinteractive.com on 22 Mar 2011 at 9:14

Attachments:

GoogleCodeExporter commented 8 years ago
Same as issue 165. The GAE/J plugin persistence process is way too fragile and 
doesn't have any handling for bidirectional relations where the keys are 
generated in-datastore. Should check if bidir and the parent is not yet 
inserted and put it without the relation, then put the child, then update the 
parent. Issue 165 has the same thing but using a String PK field

Original comment by googleco...@yahoo.co.uk on 14 Aug 2011 at 8:12

GoogleCodeExporter commented 8 years ago
The problem here is that if you persist the "mapped" side of a 1-1 bidir (i.e 
the part that *doesn't* own the relation ... in your case Parent2 ... then the 
GAE/J plugin has no idea what to do and expects the "owner" of the relation 
(i.e child) to be persistent when it receives the persist of parent.. If you 
instead call pm.makePersistent(c) then it works. IMHO it's crap handling in the 
GAE/J plugin and ought to cope with whichever side of a relation is being 
persisted ... and just set the "parent" of the EntityGroup to be the object 
passed in to persist.

Original comment by googleco...@yahoo.co.uk on 19 Aug 2011 at 1:48