Thursday, October 15, 2009

Creating A Bidrectional Owned One-To-Many

(This was originally posted to the Google App Java Google Group on September 14, 2009)

Suppose you're building a book catalog application and you want to model books and chapters.  Books contain chapters.  A chapter cannot exist without a book, so if you delete a book you want its chapters automatically deleted along with it.  You also want to each chapter to have a reference to the book that owns it.  Sounds like a bidrectional, owned, one-to-many relationship is just the thing.  First we'll set up our model objects and then we'll add some code to create a Book with 2 Chapters.

JPA:
import com.google.appengine.api.datastore.Key;
//...

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Key id;

    private String title;


    @OneToMany(mappedBy = "book", cascade = CascadeType.ALL)

    private List<Chapter> chapters = new ArrayList<Chapter>();

    // getters and setters
}


@Entity
public class Chapter {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Key id;

    private String title;
    private int numPages;

    @ManyToOne(fetch = FetchType.LAZY)
    private Book book;

    // getters and setters
}

Now let's create a book with two chapters (we'll assume someone else is creating and closing an EntityManager named 'em' for us):

Book b = new Book();
b.setTitle("JPA 4eva");
Chapter c1 = new Chapter();
c1.setTitle("Intro");
c1.setNumPages(10);
b.getChapters().add(c1);
Chapter c2 = new Chapter();
c2.setTitle("Configuration");
c2.setNumPages(9);
b.getChapters().add(c2);

em.getTransaction().begin();
try {
    em.persist(b);
    em.getTransaction().commit();
} finally {
    if (em.getTransaction().isActive()) {
        em.getTransaction().rollback();
    }
}


JDO:

import com.google.appengine.api.datastore.Key;
//...

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class Book {
 
    @PrimaryKey

    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key id;

    private String title;


    @Persistent(mappedBy = "book")
    @Element(dependent = "true")
    private List<Chapter> chapters = new ArrayList<Chapter>();

    // getters and setters
}

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")

public class Chapter {
    @PrimaryKey

    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key id;

    private String title;
    private int numPages;

    @Persistent
    private Book book;

    // getters and setters

}

Now let's create a book with two chapters (we'll assume someone else is creating and closing a PersistenceManager named 'pm' for us):

Book b = new Book();
b.setTitle("JDO 4eva");
Chapter c1 = new Chapter();
c1.setTitle("Intro");
c1.setNumPages(10);
b.getChapters().add(c1);
Chapter c2 = new Chapter();
c2.setTitle("Configuration");
c2.setNumPages(9);
b.getChapters().add(c2);

pm.currentTransaction().begin();
try {
    pm.makePersistent(b);
    pm.currentTransaction().commit();
} finally {
    if (pm.currentTransaction().isActive()) {
        pm.currentTransaction().rollback();
    }
}

37 comments:

  1. It would be great if this actually worked in App Engine.

    I get xception javax.jdo.JDOUserException: Field "com...." is declared as a reference type (interface/Object) but no implementation classes of "javax.jdo.annotations.Key" have been found!

    Any tips?

    ReplyDelete
  2. You want com.google.appengine.api.datastore.Key instead of javax.jdo.annotations.Key. I'll update the example to include the ambiguous imports.

    Thanks!
    Max

    ReplyDelete
  3. In general for a simple app, would you tend to go with JDO or JPA?

    ReplyDelete
  4. I've answered this question here:
    http://gae-java-persistence.blogspot.com/2009/10/welcome-to-gaej-persistence-blog.html?showComment=1258579955206#c998276518016732532

    ReplyDelete
  5. I made the mistake of saving the child rather than the parent in a similar simple app. It is important to manage the relationships from the owning, parent object.

    //works because b is the owning parent,
    //if I saved the chapter it wouldn't have worked (Thanks Max!)
    pm.makePersistent(b);

    ReplyDelete
  6. I have been attempting to do an owned one-to-many and have followed the example here. I have also tried for more than 10 hours experimenting with sight variations (looking at other examples) and using the debugger. I save the parent with a long list of children with no erros. No matter what I do it when I query and get the parent, the child list comes back empty. My datastore now has 10s of parent records.

    One difference is that my fields are public. Is that the problem? I don't think it could be.

    Another difference is that when I use:
    public List chapters = new ArrayList();

    I get a runtime error that says the class is not persistable, so I have
    public List chapters = new ArrayList();

    When I try not using generics on the ArrayList I get the following runtime error:
    Initializing AppEngine server
    The server is running at http://localhost:8888/
    Jan 12, 2010 8:56:59 PM com.google.appengine.tools.development.ApiProxyLocalImpl log
    SEVERE: [1263329819732000] javax.servlet.ServletContext log: Exception while dispatching incoming RPC call
    com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract java.lang.String lt.client.GreetingService.greetServer(java.lang.String)' threw an unexpected exception: org.datanucleus.jdo.exceptions.ClassNotPersistenceCapableException: The class "The class "lt.server.Book" is not persistable. This means that it either hasnt been enhanced, or that the enhanced version of the file is not in the CLASSPATH (or is hidden by an unenhanced version), or the Meta-Data/annotations for the class are not found." is not persistable. This means that it either hasnt been enhanced, or that the enhanced version of the file is not in the CLASSPATH (or is hidden by an unenhanced version), or the Meta-Data for the class is not found.

    I have also tried
    @Persistent (defaultFetchGroup = "true")
    private Book theBook;
    in the child, and that gets this warning:
    Jan 12, 2010 8:06:34 PM org.datanucleus.store.appengine.MetaDataValidator warn
    WARNING: Meta-data warning for lt.server.Lyric.theBook: The datastore does not support joins and therefore cannot honor requests to place child objects in the default fetch group. The field will be fetched lazily on first access. You can modify this warning by setting the datanucleus.appengine.ignorableMetaDataBehavior property in your config. A value of NONE will silence the warning. A value of ERROR will turn the warning into an exception.

    (I have edited the above to change from Song to Book and Lyric to Chapter to agree with your example)

    Any advise?

    ReplyDelete
  7. To clarify the above, I don't get the ClassNotPersistenceCapableException runtime error if I declare the list as:
    public List〈Chapter〉 chapters = new ArrayList〈Chapter〉();
    However, I am unable to query and get back any of the items that were in the list.

    ReplyDelete
  8. I'm sorry to hear you're having so much trouble with this. Please post your question on the Google Group for GAE Java (the link is in the App Engine Links section on the right) along with your model objects and the code you're using to query for the child objects. I'll take a look as soon as I can.

    Thanks,
    Max

    ReplyDelete
  9. I am having the exact same problem as drdbuck with the list of chapters returning as an empty.

    Do you have any updates on that issue?

    Thanks
    Per

    ReplyDelete
  10. The same here, too, but I'm using JPA:-(

    I get no errors when persisting an Entity with a List of entities marked as @OneToMany but when I query for them I just obtain empty Lists as children...
    Also, when I use the Datastore Viewer in the development console, the only Entity Kind found is the parent one, the childrent entities Kind are not shown

    ReplyDelete
  11. Sorry to hear you're having trouble with this. It sounds like your child objects aren't being saved, which would explain why they don't come back when you query for them. Is the example in this blog post working for you? If so, how is your code different?

    ReplyDelete
  12. I am not having any problems, with the exception that I cannot see the child collection in the datastore viewer in the admin console when I look at the parent entity.

    I know they are there however because when I run a query on the parent entity, i can get the collection of children. It just is not visible in datastore viewer. Is this just a limitation of the viewer?

    ReplyDelete
  13. The keys of the children entities will not show up when you're looking at the parent entities in the datastore viewer but they should show up when you're looking at child entities in the datastore viewer. If that's not the case please file an issue with your app id and a few child Keys and we'll see if we can figure out what's going on.

    Thanks,
    Max

    ReplyDelete
  14. I have a problem in storing the data in Google App Engine.Whatever data I enter,does not get stored in App engine.The code is written in Eclipse(Galileo) using jsp and Servlets.Please give stepwise instructions for the same problem.

    Thank You !!

    ReplyDelete
  15. Hi Jobseek,

    It sounds like you're having a problem that isn't related to this blog post. If that's the case please post your question to the App Engine Java Google Group:
    http://groups.google.com/group/google-appengine-java

    There's not enough info in what you've written here to provide any real assistance so please provide more details when you post.

    Thanks,
    Max

    ReplyDelete
  16. Same problem here, i'm completly stuck:

    class A with JPA annotations,

    @OneToMany(mappedBy ="a", cascade = {CascadeType.ALL })
    private final List listeB = new ArrayList();

    class B with
    @ManyToOne
    private A a;

    or without the A relationship

    I create a A, put a B in it, save the A, the B is not saved.

    It's very hard to redesign a project around the "no child lists" limitation :)

    Ahem... I've made it work... It looks that Eclipse put the Lists or sets in final.

    I've removed the "final" from all my lists and it seems to work at last !

    Spread the answer :D

    ReplyDelete
    Replies
    1. is it possible to make many "@one to many" in the same class jpa GAEJ

      Delete
  17. Can someone pls. help by providing instructions for setting up JPA on Netbeans using App Engine.

    ReplyDelete
  18. It works for parent(Book), but child classes parent field is null(book in Chapter) ... How to solve this problem ?

    ReplyDelete
  19. When are you checking the value of the parent field? If I read the Chapter back in a new transaction the Book is not null.

    ReplyDelete
  20. Sorry, that was my mistake, now it works. I was observing old data. But now, I have another problem, how can I reassign parent of child (Chapter to another book) ?

    ReplyDelete
  21. Nice tutorial,

    Can you please help me with follwing.
    I find it confusing to understand primary key things in GAE.

    I have a very simple entity Profile

    @Entity
    public class Profile implements IEntity{

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Key _id;

    @Basic
    private int _age;

    @Basic
    @Temporal(TemporalType.DATE)
    private Date _dob;


    public Profile() {}


    public Key getId() {
    return _id;
    }
    public void setId(Key id) {
    _id = id;
    }
    public int getAge() {
    return _age;
    }
    public void setAge(int age) {
    _age = age;
    }
    public Date getDob() {
    return _dob;
    }
    public void setDob(Date dob) {
    _dob = dob;
    }


    }

    To understand the auto primary key generation, I have created a simple test.

    ublic class LocalDatastoreTest extends TestCase{

    private final LocalDatastoreServiceTestConfig config = new LocalDatastoreServiceTestConfig();
    private final LocalServiceTestHelper helper = new LocalServiceTestHelper(config);
    private ProfileDao dao = new ProfileDao();

    @Before
    public void setUp() {
    //config.setNoStorage(false);
    helper.setUp();
    }

    @After
    public void tearDown() {
    helper.tearDown();
    }

    @Test
    public void testPkGeneration() {

    Profile p1 = new Profile();
    p1.setAge(20);
    p1.setDob(new Date());

    dao.setEntityManager(EMF.get().createEntityManager());
    dao.save(p1);

    assertNotNull(p1.getId());

    }
    }

    I don't understand why the Key isnt generated automatically on dao.save()

    Why the assertNotNull(p1.getId()); fails.

    ReplyDelete
  22. Hi solveme,

    I appreciate that you took the time to write up your problem I'm trying to keep the comments related to the topic of the original post and I don't see how your question is related. The gae-java forum is probably the best place to post this question.

    Thanks,
    Max

    ReplyDelete
  23. Hello. I know this is late but I'm very interested in using GAE for a number of projects and I'm currently having the exact same problem as drdbuck above. I've been experimenting and searching for issues when I came across your blog which will be very helpful for me. However I don't see an answer for this problem. Could you point me to any more information on this issue or give an explanation? I'm certain this has to be something simple that I'm missing or else this would be discussed all over the place.

    Thanks

    ReplyDelete
  24. If it helps anybody, I had same issue as mentioned by drdbuck, way out is not to use public members, instead use getter/setters with privates.

    ReplyDelete
  25. This comment has been removed by the author.

    ReplyDelete
  26. I'd appreciate a sample JPA project (like the above example) that I could test to work.

    ReplyDelete
  27. This comment has been removed by the author.

    ReplyDelete
  28. I encountered a similar issue as many people here where child entities don't seem to be persisted properly when using the JDO api. Ended up trying everything I could think to do to the model until the problem went away. Seems like GAE datastore with JDO doesn't like any field marked as 'final'. If you're having this issue, try going through and making sure no (to-be-persisted) fields are marked as 'final'.

    I don't see this requirement in any of GAE's documentation, seems like it should be added.

    ReplyDelete
  29. Hey, I was able to use your code above to store Books and Chapters....
    Thnx....

    ReplyDelete
  30. for the JPA part, how to get the book by the key?

    ReplyDelete
  31. Hi thank you very much for the sample it is very useful
    I have a question (no sure this is right the place) how to use lazy load in one to many side, I have tried but i get a empty list and I don’t know to force fetch
    Thank you again !

    ReplyDelete
  32. I answer myself, I have to access the fielf before close Entity Manager :D

    Thx

    ReplyDelete
  33. I am not getting reference of child key as foreign key in parent class with these code.
    do you have any other link or example ?

    ReplyDelete
  34. Max,

    There seems to be allot of folks have issues with the this one-to-many owned/unowned bidirectional
    relationship in Appengine. This past weekend, JDO had its way with me..this one-many stuff
    frustrated me and kicked my butt.. it still does strange things, I have strange errors nothing
    consistently works .. etc.. see other in this post saying some of the same things I am experiencing
    but no solution from anyone ...

    the example you gave above simply does not work in GAE.. not for me at least..and any other
    of my seasoned colleagues..
    Help?

    ReplyDelete
  35. I have to agree with what others have said here, it simply does not work in the latest GAE development environment. The child items are not saved.

    I've come across reports that it does work in production but that there is a bug in the dev env only. I suspect I'll never know, since I'm not going to deploy something that doesn't work right in front of me! I'm going to abandon collections here and just use ids all over the place, old style.

    ReplyDelete
  36. Hey thanks for the tutorial. I've done that one-to-many relations persistence.

    My question however is how do i select only chapters of a specific book only since all the chapters are stores in the same entity kind but there's not reference("foreign key") of the corresponding book.

    In the normal sql i would for instance query it like "select * from chapters where book="book_id". What's the equivalent of this query in appengine? Thanks alot.

    ReplyDelete