Wednesday 16 September 2009

Google App Engine Java (GAE) Persistence Gotcha

Working with GAE/J (Google App Engine for Java) I hit a snag whilst trying to upload some data into my google web app. I was using a set of tricks and tools (more of which later) to reconstruct some data from an XML file description of the data generated with XStream.

As a result the data being mapped into the application had to some extent to match the objects which had produced it. As a result I have a SortedMap field in one of my data objects.

@PersistenceCapable(identityType=IdentityType.APPLICATION)
public class WavaBase implements Comparable <WavaBase> {
    @PrimaryKey
    @Persistent
    private String id;
    @Persistent
    private String sex;
    @Persistent
    private Double standardInSeconds;
    @Persistent
    private String distance;
    @Persistent
    private Double distanceInKilometers;

    private SortedMap<Integer,WavaData> wavaData;
    @Persistent(mappedBy="wavaBase")
    private SortedSet<WavaData> wavaDataSet;
     ...
}

The sorted map is not annotated as @Persistent but here is the gotcha. GAE will still attempt to persist it resulting in a most unhelpful warning

java.lang.UnsupportedOperationException: FK Maps not supported.
    at org.datanucleus.store.appengine.DatastoreManager.newFKMapStore(DatastoreManager.java:401)
    at org.datanucleus.store.mapped.MappedStoreManager.getBackingStoreForMap(MappedStoreManager.java:766)
    at org.datanucleus.store.mapped.MappedStoreManager.getBackingStoreForField(MappedStoreManager.java:637)
    at org.datanucleus.sco.backed.SortedMap.(SortedMap.java:82)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    ...

Looking at the trace you can see that datanucleus is attemting to deal with the SortedSet even though it is not marked for persistence.

To ensure that a private member of a @PersistenceCapable class is not processed by the persistence store you need to "explicitly" mark the member as @NotPersistent, this tells the persistence layer to ignore the member.

So the class becomes

@PersistenceCapable(identityType=IdentityType.APPLICATION)
public class WavaBase implements Comparable <WavaBase> {
    @PrimaryKey
    @Persistent
    private String id;
    @Persistent
    private String sex;
    @Persistent
    private Double standardInSeconds;
    @Persistent
    private String distance;
    @Persistent
    private Double distanceInKilometers;

    @NotPersistent
    private SortedMap<Integer,WavaData> wavaData;
    @Persistent(mappedBy="wavaBase")
    private SortedSet<WavaData> wavaDataSet;
     ...
}

There may be a way to switch the default handling of members of a @PersistenceCapable class so that members need to be explicitly included in persistence rather than excluded, but I haven't found it yet.

2 comments:

  1. wow, thanks, good catch. some google engineers are monkeys and wasted everyone's time w/ poor coding. good thing you posted this posting.

    ReplyDelete
  2. Great, Thank you very much for your great support.

    ReplyDelete