Question

I'm trying to persist a one-to-many owned relationship with bidirectional navigation in GAE using JDO.

I manually add the Contact to User class, and I would expect that in the end the Contact will have a reference to the parent User object.

  • If I configure this manually before I persist the parent, I get an exception: org.datanucleus.store.appengine.DatastoreRelationFieldManager.checkForParentSwitch(DatastoreRelationFieldManager.java:204)
  • After the User object persistence the parent reference is not updated.
  • After the Contact object is retrieved from the datastore using the key the parent reference is not updated.

I don't understand where my mistake is.

package test;

import java.util.ArrayList;
import java.util.List;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import com.google.appengine.api.datastore.Key;

public class DatastoreJdoTest extends LocalServiceTestCase {
    @Autowired
    @Qualifier("persistenceManagerFactory")
    PersistenceManagerFactory pmf;

    @Test
    public void testBatchInsert() {
        Key contactKey;
        PersistenceManager pm = pmf.getPersistenceManager();
        try {
            pm.currentTransaction().begin();
            User user = new User();
            Contact contact = new Contact("contact1");
            user.contacts.add(contact);

            /*
             * With this an exception is thrown
             * 
             * Detected attempt to establish User(1)/Contact(2) as the parent of
             * User(1) but the entity identified by User(1) has already been
             * persisted without a parent. A parent cannot be established or
             * changed once an object has been persisted.
             * org.datanucleus.store.appengine.FatalNucleusUserException:
             * Detected attempt to establish User(1)/Contact(2) as the parent of
             * User(1) but the entity identified by User(1) has already been
             * persisted without a parent. A parent cannot be established or
             * changed once an object has been persisted. at
             * org.datanucleus.store
             * .appengine.DatastoreRelationFieldManager.checkForParentSwitch
             * (DatastoreRelationFieldManager.java:204)
             */
            //contact.user = user;
            Assert.assertNull(contact.key);
            pm.makePersistent(user);
            Assert.assertNotNull(contact.key);

            pm.currentTransaction().commit();

            contactKey = contact.key;
            //this assertion is broken. why ? 
            //Assert.assertNotNull(contact.user);
        } finally {
            if (pm.currentTransaction().isActive()) {
                pm.currentTransaction().rollback();
            }
        }
        Contact contact2 = pm.getObjectById(Contact.class, contactKey);
        Assert.assertNotNull(contact2);
        //this assertion is broken. why the contact don't store the parent user ?
        Assert.assertNotNull(contact2.user);
    }
}

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
class User {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    public Key key;
    @Persistent
    public String name;
    @Persistent
    public List<Contact> contacts = new ArrayList<Contact>();
}

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
class Contact {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    Key key;
    @Persistent
    public String contact;
    @Persistent(mappedBy = "contacts", dependent = "true")
    public User user;

    public Contact(String contact) {
        this.contact = contact;
    }
}
Was it helpful?

Solution

According to App Engine docs, you should specify "mappedBy" in the owner of your relationship.

You may also want to read Max Ross's article or to have a look at my code which accesses parent (Discussion) from a child object (Message) which is fetched from a query

OTHER TIPS

Just posting the code with the correction Dmitry pointed out for easier reading:

import java.util.ArrayList;
import java.util.List;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import com.google.appengine.api.datastore.Key;

public class DatastoreJdoTest extends LocalServiceTestCase {
 @Autowired
 @Qualifier("persistenceManagerFactory")
 PersistenceManagerFactory pmf;

 @Test
 public void testBatchInsert() {
  Key contactKey;
  PersistenceManager pm = pmf.getPersistenceManager();
  try {
   pm.currentTransaction().begin();
   User user = new User();
   Contact contact = new Contact("contact1");
   user.contacts.add(contact);

   /*
    * With this an exception is thrown
    * 
    * Detected attempt to establish User(1)/Contact(2) as the parent of
    * User(1) but the entity identified by User(1) has already been
    * persisted without a parent. A parent cannot be established or
    * changed once an object has been persisted.
    * org.datanucleus.store.appengine.FatalNucleusUserException:
    * Detected attempt to establish User(1)/Contact(2) as the parent of
    * User(1) but the entity identified by User(1) has already been
    * persisted without a parent. A parent cannot be established or
    * changed once an object has been persisted. at
    * org.datanucleus.store
    * .appengine.DatastoreRelationFieldManager.checkForParentSwitch
    * (DatastoreRelationFieldManager.java:204)
    */
   //contact.user = user;
   Assert.assertNull(contact.key);
   pm.makePersistent(user);
   Assert.assertNotNull(contact.key);

   pm.currentTransaction().commit();

   contactKey = contact.key;
   //this assertion is broken. why ? 
   //Assert.assertNotNull(contact.user);
  } finally {
   if (pm.currentTransaction().isActive()) {
    pm.currentTransaction().rollback();
   }
  }
  Contact contact2 = pm.getObjectById(Contact.class, contactKey);
  Assert.assertNotNull(contact2);
  //this assertion is broken. why the contact don't store the parent user ?
  Assert.assertNotNull(contact2.user);
 }
}

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
class User {
 @PrimaryKey
 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
 public Key key;
 @Persistent
 public String name;
 @Persistent(mappedBy = "user", dependent = "true")
 public List<Contact> contacts = new ArrayList<Contact>();
}

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
class Contact {
 @PrimaryKey
 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
 Key key;
 @Persistent
 public String contact;
 @Persistent
 public User user;

 public Contact(String contact) {
  this.contact = contact;
 }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top