Question

I have three @PC classes:

@PersistenceCapable
class A {
  @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
  @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
  private String                  id;

  @Persistent @Embedded
  private B b;

  public void setB(B b){
    this.b=b;
  }
}

@PersistenceCapable @EmbeddedOnly
class B {
  @Persistent
  private String someInfo;
  @Persistent
  private C c;

  public void setC(C c){
    this.c=c;
  }
}

@PersistenceCapable
class C {
  @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
  @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
  private String                  id;

  @Persistent
  private String value;

  public void setValue(String value){
    this.value=value;
  }
}

I want to achieve that B is persisted to same entity as A while holding a reference to C but GAE does not let me, I get a following exception on commit:

Detected attempt to establish A(1) as the parent of C(2) but the entity identified by C(2) has already been persisted without a parent.  A parent cannot be established or changed once an object has been persisted.

in this code:

A a = new A();
B b = new B();
C c = new C();
c.setValue("foo");
b.setC(c);
a.setB(b);

m.makePersistent(a);

additionally: a look into DatastoreViewer shows me that C has been persisted! But A is missing. This might happen because I do not explicitelly rollback the transaction on exception which is not relevant in this case, but reveals that C is written before its parent A.

what am I missing? Tx

Update 2:

as suggested I have enabled transaction explicitely:

Transaction tx = pm.currentTransaction();
try {
  tx.begin();
  pm.makePersistent(a);
  tx.commit();
} finally {
  if (tx.isActive()) {
    tx.rollback();
  }
  pm.close();
}

same exception was thrown as when doing .makePersistent() w/o explicit transaction. Then I set disabled the global cross tx option in JDO config:

<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="false"/>

and now get a different exception with a possible hint:

cross-group transaction need to be explicitly specified, see 
TransactionOptions.Builder.withXGfound both Element {
  type: "A"
  id: 1
}
and Element {
  type: "C"
  id: 2
}
Was it helpful?

Solution 2

ok FINALLY I got the point,

yet I think that DataStore's JDO implementation ( I don't know if its DataNucleus job) miss there something. According to transactions on DataStore usually only entities along an ancestor can be persisted in one go, exceptionally GAE claims to support Cross-Group Transactions which is limited by number but can persist unrelated entity paths. Both did not apply in my case.

The poor solution is now to enforce unowned relationship via a Key (GAE proprietary), which is the only possible kandidate to describe ancestor path so that DataStore gets it right is following extension to my POJOs:

class A {
  ...
  private String naturalPK;
  public String getNaturalPK(){
    return naturalPK;
  }
  ...
}

class C {
  ...
  public void setId(String id){
    this.id=id;
  }
  ...
}

and the persistency-code:

  tx.begin();

  // we have to assign parent/child keys to enforce ancestor path
  Key parentKey = KeyFactory.createKey("A", A.getNaturalPK());
  a.setId(KeyFactory.keyToString(parentKey));

  // now build child key
  a.getB().getC().setId(KeyFactory.createKeyString(parentKey, "C", A.getNaturalPK()));

  pm.makePersistent(offerer);
  tx.commit(); // works.

one issue is that I cannot use surrogate keys here, the other thing is that I dont want non-JDO and non-BO code to be in my POJOs, so the parent-child relationship has to be established somewhere in die JDO-DAO implementation. I suppose that DataNucleus GAE port is doing somewhat inaccurate here as the persistency graph looks to be reversed :)

OTHER TIPS

I assume you're problem vanishes if you set up a transaction boundary for this to work. Although the documentation claims that what you do should be possible, I assume you have set datanucleus.appengine.autoCreateDatastoreTxns to true in your jdoconfig.xml.
Thus, C is stored in its own transaction with an entity group of it's own. In a second transaction, you store A and thus try to reassign C to A's entity groupy which is forbidden:

because entity groups can only be assigned when the entities are created

So: set up a transaction (as recommended).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top