Can't get owned relationship working with JDO and subclass

6 views
Skip to first unread message

Rick Horowitz

unread,
Nov 15, 2009, 11:40:51 PM11/15/09
to Google App Engine
I'm trying to persist a simple class relationship using JDO. My code
is listed below and here's a brief overview of what I'm trying to do:

I have 3 classes:

Person, Customer (extends Person), and Address (referenced from
Person). If I create a Person instance and an Address instance and
call setAddress() on Person, then call
PersistenceManager.makePersistent(), the objects get saved correctly.

However, if I create a Customer instance and an Address instance, I
get the following error message:

java.lang.IllegalArgumentException: can't operate on multiple entity
groups in a single transaction. found both Element {
type: "Customer"
id: 1
}
and Element {
type: "Address"
id: 2
}

I am doing this from within a JDO transaction.

Here's the code:

First, the server-side code that attempts to create and persist the
objects:
-----------------------------------------------------------------------------------------------------------

PersistenceManager pm = PMF.get().getPersistenceManager();
Transaction txn = pm.currentTransaction();
try {
txn.begin();
Address a = new Address("15 Post Road", "", "Anytown", "New York",
"USA", "03801");
Customer c = new Customer();
c.setAddress(a);
c.setFirstName("John");
c.setLastName("Doe");
pm.makePersistent(c);
txn.commit();
} finally {
if (txn.isActive()) {
txn.rollback();
}
pm.close();
}

Person:
-----------

@PersistenceCapable(identityType = IdentityType.APPLICATION)
@Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE)
public abstract class Person implements Serializable {

public static enum PERSON_KEYS {
firstName,
lastName,
workEmail,
phoneNbr
}
static final long serialVersionUID = 100L;

@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
@Extension(vendorName = "datanucleus", key = "gae.encoded-pk",
value="true")
private String key;
private String firstName;
private String lastName;
private String middleInitial;
private String workEmail;
private String homeEmail;
private String phoneNbr;
@Persistent
private Address address = new Address();
private String salutation;

public Person() {
}

public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

public String getWorkEmail() {
return (this.workEmail);
}

public void setWorkEmail(String workEmail) {
this.workEmail = workEmail;
}

public String getHomeEmail() {
return homeEmail;
}

public void setHomeEmail(String homeEmail) {
this.homeEmail = homeEmail;
}

@NotPersistent
public String getEmailAddress() {
String emailAddr = getWorkEmail();
if ((emailAddr == null) || (emailAddr.length() ==0))
emailAddr = getHomeEmail();
return emailAddr;
}

public void setEmailAddress(String emailAddress) {
setWorkEmail(emailAddress);
}

@NotPersistent
public String getFullName() {
StringBuffer sb = new StringBuffer();
if (getFirstName() != null) {
sb.append(getFirstName());
sb.append(" ");
}
if ((getMiddleInitial() != null) && (getMiddleInitial().length() >
0)) {
sb.append(getMiddleInitial());
sb.append(" ");
}
if (getLastName() != null) {
sb.append(getLastName());
}
return sb.toString();
}

public String getFirstName() {
return (this.firstName);
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return (this.lastName);
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public Address getAddress() {


public void setAddress(Address address) {
this.address = address;
}

public String getMiddleInitial() {
return middleInitial;
}

public void setMiddleInitial(String middleInitial) {
this.middleInitial = middleInitial;
}

public String getPhoneNbr() {
return phoneNbr;
}

public void setPhoneNbr(String phoneNbr) {
this.phoneNbr = phoneNbr;
}
}

Customer:
---------------

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Customer extends Person implements Serializable {

static final long serialVersionUID = 100L;

public static enum CUSTOMER_KEYS {
username,
password,
companyName,
}

public static int FT_STATUS_OK = 0;
public static int FT_STATUS_ACTIVE_FREE_TRIAL = 1;
public static int FT_STATUS_ACTIVE_FREE_TRIAL_EXT = 2;
public static int FT_STATUS_RECENT_FREE_TRIAL = 4;
public static int FT_STATUS_RECENT_FREE_TRIAL_EXT = 8;

private String username;
private String password;
private String companyName;
private int freeTrialStatus;

public Customer() {
}

public String getUsername() {
return (this.username);
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return (this.password);
}

public void setPassword(String password) {
this.password = password;
}

public int getFreeTrialStatus() {
return freeTrialStatus;
}

public String getCompanyName() {
return companyName;
}

public void setCompanyName(String companyName) {
this.companyName = companyName;
}
}

Address
------------

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Address implements java.io.Serializable {


static final long serialVersionUID = 100L;

public static enum ADDRESS_KEYS {
address1, address2, city, state, country, postalCode
}

@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
@Extension(vendorName = "datanucleus", key = "gae.encoded-pk",
value="true")
private String key;
private Long version;
private String address1;
private String address2;
private String city;
private String state;
private String country;
private String postalCode;

public Address() {
}

/**
* Construct the address
*
* @param address1
* Address line 1
* @param address2
* Address line 2
* @param city
* The city
* @param state
* The state
* @param country
* The country
* @param postalCode
* Postal/ZIP code
*/
public Address(String address1, String address2, String city, String
state,
String country, String postalCode) {

this.address1 = address1;
this.address2 = address2;
this.city = city;
this.state = state;
this.country = country;
this.postalCode = postalCode;
}

public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

public Long getVersion() {
return version;
}

public void setVersion(Long version) {
this.version = version;
}

public void setAddress1(String address1) {
this.address1 = address1;
}

public String getAddress1() {
return this.address1;
}

public void setAddress2(String address2) {
this.address2 = address2;
}

public String getAddress2() {
return this.address2;
}

public void setCity(String city) {
this.city = city;
}

public String getCity() {
return this.city;
}

public void setState(String state) {
this.state = state;
}

public String getState() {
return this.state;
}

public void setCountry(String country) {
this.country = country;
}

public String getCountry() {
return this.country;
}

public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}

public String getPostalCode() {
return this.postalCode;
}

}

The full stack trace for the exception:
------------------------------------------------------

Caused by: java.lang.IllegalArgumentException: can't operate on
multiple entity groups in a single transaction. found both Element {
type: "Customer"
id: 1
}
and Element {
type: "Address"
id: 2
}

at
com.google.appengine.api.datastore.DatastoreApiHelper.translateError
(DatastoreApiHelper.java:33)
at com.google.appengine.api.datastore.DatastoreApiHelper.makeSyncCall
(DatastoreApiHelper.java:60)
at com.google.appengine.api.datastore.DatastoreServiceImpl$2.run
(DatastoreServiceImpl.java:173)
at
com.google.appengine.api.datastore.TransactionRunner.runInTransaction
(TransactionRunner.java:30)
at com.google.appengine.api.datastore.DatastoreServiceImpl.put
(DatastoreServiceImpl.java:161)
at com.google.appengine.api.datastore.DatastoreServiceImpl.put
(DatastoreServiceImpl.java:141)
at com.google.appengine.api.datastore.DatastoreServiceImpl.put
(DatastoreServiceImpl.java:137)
at
org.datanucleus.store.appengine.RuntimeExceptionWrappingDatastoreService.put
(RuntimeExceptionWrappingDatastoreService.java:105)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.put
(DatastorePersistenceHandler.java:172)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.put
(DatastorePersistenceHandler.java:112)
at
org.datanucleus.store.appengine.DatastorePersistenceHandler.insertObjects
(DatastorePersistenceHandler.java:239)
at
org.datanucleus.store.appengine.DatastorePersistenceHandler.insertObject
(DatastorePersistenceHandler.java:225)
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.store.mapped.mapping.PersistenceCapableMapping.setObjectAsValue
(PersistenceCapableMapping.java:604)
at
org.datanucleus.store.mapped.mapping.PersistenceCapableMapping.setObject
(PersistenceCapableMapping.java:364)
at org.datanucleus.store.appengine.DatastoreRelationFieldManager
$1.setObjectViaMapping(DatastoreRelationFieldManager.java:132)
at org.datanucleus.store.appengine.DatastoreRelationFieldManager
$1.apply(DatastoreRelationFieldManager.java:108)
at
org.datanucleus.store.appengine.DatastoreRelationFieldManager.storeRelations
(DatastoreRelationFieldManager.java:80)
at
org.datanucleus.store.appengine.DatastoreFieldManager.storeRelations
(DatastoreFieldManager.java:795)
at
org.datanucleus.store.appengine.DatastorePersistenceHandler.insertPostProcess
(DatastorePersistenceHandler.java:288)
at
org.datanucleus.store.appengine.DatastorePersistenceHandler.insertObjects
(DatastorePersistenceHandler.java:241)
at
org.datanucleus.store.appengine.DatastorePersistenceHandler.insertObject
(DatastorePersistenceHandler.java:225)
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)
... 36 more

Rick Horowitz

unread,
Nov 19, 2009, 4:44:46 PM11/19/09
to Google App Engine
Does anyone have any idea why this won't work? I've tried every option
I can think of and it still doesn't work. Would really appreciate some
help from someone knowledgeable about JDO and gae. I'm assuming it's
pretty basic to persist a subclass object whose superclass (base
class) references an owned object, but gae doesn't seem to like it. I
tried the base class as abstract and concrete, and tried the keys as
type Key and as encoded String. No luck.

Thanks very much for any help.

Rick

Ikai L (Google)

unread,
Nov 19, 2009, 5:03:18 PM11/19/09
to google-a...@googlegroups.com
The current version won't support inheritance, which may explain why this isn't working. It looks like fixes for inheritance will be going out when datanucleus is updated:

The stack trace looks different, though, but that may be because you are using a transaction, and the transaction manager is taking a look at the key that is being generated and does not recognize "Address" as having "Customer" as an ancestor.

I'm not sure why you need a transaction here, though. It doesn't look like Customer or Address will be out of sync. You could also accomplish the same thing using an embedded class, though I suppose that wouldn't work if multiple customers had the same address and addresses changed frequently (though you could solve this by updating all matching embedded addresses if an address changes in a transaction, making the Address the parent of the Customer).  

--

You received this message because you are subscribed to the Google Groups "Google App Engine" group.
To post to this group, send email to google-a...@googlegroups.com.
To unsubscribe from this group, send email to google-appengi...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/google-appengine?hl=.





--
Ikai Lan
Developer Programs Engineer, Google App Engine

Rick Horowitz

unread,
Nov 19, 2009, 8:22:25 PM11/19/09
to google-a...@googlegroups.com
Thank you for responding. This is not good, but I guess it could be worse. Google could be ignoring these problems instead of fixing them. One more question. What's the right way to install appengine-orm-1.0.4.rc1.zip with the Eclipse App Engine SDK 1.2.6? Do I just replace all the jars in the SDK with the jars from appengine-orm-1.0.4.rc1.zip?

Thanks very much.

Rick

Rick Horowitz

unread,
Nov 20, 2009, 7:36:05 AM11/20/09
to Google App Engine
I just examined the jars in some detail and see that there is only 1
jar changed: datanucleus-appengine-1.0.3.jar -> datanucleus-
appengine-1.0.4.RC1.jar. I'll upgrade that into the Eclipse plugin and
do some tests. Unfortunately, my data model uses owned relationships
from the base class, which I guess doesn't work just yet. I'll report
back. Thanks.
> ...
>
> read more »

Rick Horowitz

unread,
Nov 20, 2009, 10:33:06 AM11/20/09
to Google App Engine
Well it partly worked, but enough to get by for development purposes
for awhile, so that's pretty good. I removed datanucleus-
appengine-1.0.3.jar from the Eclipse plugin and replaced it with
appengine-1.0.4.RC1.jar. First, I tried my data model unchanged and it
did not work. I got the same error message:

Caused by: java.lang.IllegalArgumentException: can't operate on
multiple entity groups in a single transaction. found both Element {
type: "Customer"
id: 1
}

and Element {
type: "Address"
id: 2

}

So, then I moved the reference to Address from the base class,
"Person" to the subclass, "Customer", and I was able to persist a
Customer. I'm assuming that any owned relationships will only work if
they emanate from the concrete class that you are trying to persist;
not a superclass. But I haven't tried this with, for example, a 3
level inheritance hierarchy, so I can't vouch for that. If I have to
use this type of inheritance in my app, I will post another message to
let people know how it turned out.

In the mean time, I'm hoping that we'll see an update to fix this
owned relationship by a superclass problem soon.

Rick
> ...
>
> read more »

Rick Horowitz

unread,
Nov 22, 2009, 3:23:52 PM11/22/09
to Google App Engine
I discovered I cannot query (filter on) a superclass field. I described this problem as a comment on App Engine Issue 25 - http://code.google.com/p/datanucleus-appengine/issues/detail?id=25#c24

Anyone that knows what is going on, please reply.

Thanks very much for any help,

Rick

> ...
>
> read more »

Reply all
Reply to author
Forward
0 new messages