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=
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=
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;
//...
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(
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().
} finally {
if (pm.currentTransaction().
pm.currentTransaction().
}
}
It would be great if this actually worked in App Engine.
ReplyDeleteI 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?
You want com.google.appengine.api.datastore.Key instead of javax.jdo.annotations.Key. I'll update the example to include the ambiguous imports.
ReplyDeleteThanks!
Max
In general for a simple app, would you tend to go with JDO or JPA?
ReplyDeleteI've answered this question here:
ReplyDeletehttp://gae-java-persistence.blogspot.com/2009/10/welcome-to-gaej-persistence-blog.html?showComment=1258579955206#c998276518016732532
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.
ReplyDelete//works because b is the owning parent,
//if I saved the chapter it wouldn't have worked (Thanks Max!)
pm.makePersistent(b);
It's 4 years later but I had the same issue. Thanks Max and Mark :)
DeleteI 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.
ReplyDeleteOne 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?
To clarify the above, I don't get the ClassNotPersistenceCapableException runtime error if I declare the list as:
ReplyDeletepublic List〈Chapter〉 chapters = new ArrayList〈Chapter〉();
However, I am unable to query and get back any of the items that were in the list.
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.
ReplyDeleteThanks,
Max
I am having the exact same problem as drdbuck with the list of chapters returning as an empty.
ReplyDeleteDo you have any updates on that issue?
Thanks
Per
The same here, too, but I'm using JPA:-(
ReplyDeleteI 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
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?
ReplyDeleteI 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.
ReplyDeleteI 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?
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.
ReplyDeleteThanks,
Max
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.
ReplyDeleteThank You !!
Hi Jobseek,
ReplyDeleteIt 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
Same problem here, i'm completly stuck:
ReplyDeleteclass 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
is it possible to make many "@one to many" in the same class jpa GAEJ
DeleteCan someone pls. help by providing instructions for setting up JPA on Netbeans using App Engine.
ReplyDeleteIt works for parent(Book), but child classes parent field is null(book in Chapter) ... How to solve this problem ?
ReplyDeleteWhen are you checking the value of the parent field? If I read the Chapter back in a new transaction the Book is not null.
ReplyDeleteSorry, 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) ?
ReplyDeleteNice tutorial,
ReplyDeleteCan 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.
Hi solveme,
ReplyDeleteI 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
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.
ReplyDeleteThanks
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.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteI'd appreciate a sample JPA project (like the above example) that I could test to work.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteI 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'.
ReplyDeleteI don't see this requirement in any of GAE's documentation, seems like it should be added.
Hey, I was able to use your code above to store Books and Chapters....
ReplyDeleteThnx....
for the JPA part, how to get the book by the key?
ReplyDeleteHi thank you very much for the sample it is very useful
ReplyDeleteI 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 !
I answer myself, I have to access the fielf before close Entity Manager :D
ReplyDeleteThx
I am not getting reference of child key as foreign key in parent class with these code.
ReplyDeletedo you have any other link or example ?
Max,
ReplyDeleteThere 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?
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.
ReplyDeleteI'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.
Hey thanks for the tutorial. I've done that one-to-many relations persistence.
ReplyDeleteMy 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.