tag:blogger.com,1999:blog-41433082682711136022024-03-17T20:01:29.299-07:00Google App Engine Java PersistenceApp Engine Java persistence tips from an App Engine developer.Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.comBlogger16125tag:blogger.com,1999:blog-4143308268271113602.post-20405507106286647512011-11-01T18:07:00.000-07:002011-11-01T18:07:41.032-07:00DataNucleus App Engine Plugin v2.0.0-RC1 available for testing<br />
<div style="background-color: white; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; color: #222222; font-family: Arial, Helvetica, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; outline-color: initial; outline-style: initial; outline-width: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; text-align: left; vertical-align: baseline;">
JPA 2, JDO 3, unowned relationships, and tons of bug fixes.</div>
<div style="background-color: white; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; color: #222222; font-family: Arial, Helvetica, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; outline-color: initial; outline-style: initial; outline-width: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; text-align: left; vertical-align: baseline;">
<br /></div>
<a href="https://plus.google.com/102837845875216819095/posts/9U9VFWqhPx7" style="background-color: white; border-bottom-width: 0px; border-color: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; color: #1155cc; cursor: pointer; font-family: Arial, Helvetica, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; outline-color: initial; outline-style: initial; outline-width: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; text-align: left; text-decoration: none; vertical-align: baseline;" target="_blank">https://plus.google.com/<wbr></wbr>102837845875216819095/posts/<wbr></wbr>9U9VFWqhPx7</a>Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com18tag:blogger.com,1999:blog-4143308268271113602.post-37682135372763833542010-03-21T17:50:00.000-07:002010-03-21T17:50:22.742-07:00Executing Simple Joins Across Owned RelationshipsAs promised, today I'm going to explain how to use simple joins with owned relationships. This requires some additional setup because the physical layout of an owned relationship is not the same as the physical layout of an unowned relationship. Specifically, with an unowned relationship, the keys of the child entities are stored in a property on the parent, whereas with an owned relationship the keys of the child entities are not stored in a property on the parent - ownership is instead modeled using the hierarchical structure of the child key. Simple joins depend on the physical layout we get with unowned relationships, so if we're going to use simple joins with owned relationships we're going to need to change the way we store them.<br />
<br />
Fortunately, as of SDK 1.3.1, there is a configuration option that changes the physical layout of owned relationships to match the physical layout of unowned relationships while preserving the hierarchical structure of the child key. Nice! You can enable this option by adding the following to your config:<br />
<br />
<property name="datanucleus.appengine.storageVersion" value="WRITE_OWNED_CHILD_KEYS_TO_PARENTS"/><br />
<br />
If you're using JPA, add this to persistence.xml. If you're using JDO, add this to jdoconfig.xml. If you don't have any existing data in production you're ready to move on to the examples. If you do have existing data there's more work to do: data migration. If you're like me then you get sad, then angry, then hungry, then sad again whenever you see those words so I apologize for just throwing them in your face like that, but if you have existing data there's really no way around it. See, setting this config option will cause all newly created and updated parents to be written with a property containing child keys, but all your existing data is still sitting there in the old layout. If you want the results of your simple joins to be accurate (and you do want this, right?) you're going to need to migrate all your existing data. I'll explain how to do this in a later post (note that I said 'a later post' not 'the next post' - I may not get to this for awhile).<br />
<br />
Let's go back to our college example. We have Students, Credits, and Honors Thesis. Every Student has zero or more Credits and zero or one Honors Thesis.<br />
<br />
JPA:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@Entity</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class Student {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Id</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @GeneratedValue(strategy = GenerationType.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Long id;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private String year;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @OneToMany(cascade = CascadeType.ALL)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private List<Credit> credits;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private HonorsThesis thesis;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@Entity</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class Credit {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Id</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @GeneratedValue(strategy = GenerationType.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Key key;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private String department;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@Entity</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class HonorsThesis {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Id</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @GeneratedValue(strategy = GenerationType.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Key key;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private boolean complete;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<br />
<br />
JDO:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@PersistenceCapable(detachable = "true")</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class Student {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @PrimaryKey</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Long id;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private String year;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Element(dependent = "true")</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private List<Credit> credits;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Persistent</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private HonorsThesis thesis;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@PersistenceCapable(detachable = "true")</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class Credit {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @PrimaryKey</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Key key;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private String department;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@PersistenceCapable(detachable = "true")</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class HonorsThesis {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @PrimaryKey</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Key key;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private boolean complete;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
---------------------------------------<br />
Here's a query that returns all Seniors with at least one credit from the Philosophy department:<br />
JPA:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Query q = em.createQuery("select from " + Student.class.getName() </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + " s JOIN s.credits c where "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "c.department = 'Philosophy' and "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "s.year = 'Senior'");</span><br />
<br />
JDO:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Query q = pm.newQuery("select from " + Student.class.getName() </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + " where "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "credits.contains(c) && c.department == 'Philosophy' && "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "year == 'Senior'");</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">q.declareVariables(Credit.class.getName() + " c");</span><br />
<br />
---------------------------------------<br />
<br />
And here's a query that returns all Seniors who have completed an Honors Thesis:<br />
JPA:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Query q = em.createQuery("select from " + Student.class.getName() </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + " s JOIN s.thesis t where "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "t.complete = true and "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "s.year = 'Senior'");</span><br />
<br />
JDO:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Query q = pm.newQuery("select from " + Student.class.getName() + " where "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "thesis == t && t.complete == true && "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "year == 'Senior'");</span><br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">q.declareVariables(HonorsThesis.class.getName() + " t");</span></div><br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">---------------------------------------</div><div>Unlike the unowned relationship examples we covered <a href="http://gae-java-persistence.blogspot.com/2010/02/truth-about-joins.html">in the last post</a>, we're now firmly back in the land of standard JPA and JDO. Good! That's where we want to be. Give it a try and let me know what you think! In the next post, hopefully less than 6 weeks away this time, I'll explain how simple joins work under the hood.</div>Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com50tag:blogger.com,1999:blog-4143308268271113602.post-9750637953282617482010-02-12T13:39:00.000-08:002010-03-21T16:53:26.787-07:00The Truth About JoinsWhenever I get the opportunity to talk to my peers about the coolness of App Engine it always pains me to have to say "...but you can't do joins." I know how useful joins are because I spent many many years using them before I started working on App Engine. Lately I've been thinking that there has to be a way to make it work. The App Engine datastore offers a guarantee that query performance scales with the size of the result set, not the size of the data set, so a query that returns 100 results should take the same amount of time whether you have a thousand rows or a million. That means we can't compute a full cross-product at time-of-query because that would require looking at all the data in the n tables we're joining, and if we look at all the data our performance is now tied to the size of the data set. Still, it feels like we ought to be able to do something here.<br />
<br />
What if we set our sights a little bit lower? Sure, n-way joins with arbitrary filters and sort orders would be ideal, but isn't something better than nothing in this area? It turns out that with some additional restrictions in place it is possible to execute joins where the performance scales with the size of the result set, and as of the <a href="http://googleappengine.blogspot.com/2010/02/app-engine-sdk-131-including-major.html">recently released SDK 1.3.1</a> this feature, which I'm calling "simple joins," is available to beta testers.<br />
<br />
Now, before you start licking your chops let me give you all the bad news (there's plenty of it). First, simple joins are only available to JPA and JDO users. Why? Because JPA and JDO already have an established API for joins and API design is really really hard. I try to avoid it whenever possible. Second, simple joins only support equality filters. Ouch, right? Third, simple joins can only be sorted by the property on which you're joining. Double ouch. Fourth, if you're using owned relationships, in order to use simple joins you're going to need to upgrade your storage version and migrate existing data. This post is already long enough so I'm just going to focus on simple joins across unowned relationships. I'll cover storage versions, data migration, and joining across owned relationships in my next post, I promise.<br />
<br />
I know that was a lot of bad news, but my theory is that even with all that bad news, simple joins are sufficiently useful. I'm making them available to try and prove that theory. Ready to try it out?<br />
<br />
Let's model a college. We have Students, Courses, and Dorms. Every Student is associated with zero or one Dorm and zero or more Courses.<br />
<br />
JPA:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@Entity</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class Student {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Id</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @GeneratedValue(strategy = GenerationType.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Long id;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private String year;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Basic</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private List<Key> courses = new ArrayList<Key>();</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @OneToMany</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Column(name = "courses", insertable = false, updatable = false)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private List<Course> coursesAlias;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Basic</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Key dorm;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @OneToOne(fetch = FetchType.LAZY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Column(name = "dorm", insertable = false, updatable = false)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Dorm dormAlias;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@Entity</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class Course {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Id</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @GeneratedValue(strategy = GenerationType.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Key key;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private String department;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@Entity</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class Dorm {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Id</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @GeneratedValue(strategy = GenerationType.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Key key;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private String campus;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<br />
Yes, there's something funny (and by funny I mean hacky) going on with the courseAlias and dormAlias fields. The JPQL compiler won't let you join on a Key or a List<Key> field because these aren't OneToOne or OneToMany fields. To work around this I've created a read-only alias of the appropriate type. Your application code should always read from and write to the 'courses' and 'dorm' fields while your join queries should read from and write to the 'coursesAlias' and 'dormAlias' fields. Once we have proper support for unowned relationships all this nonsense will go away.<br />
<br />
WARNING: There's a <a href="http://code.google.com/p/datanucleus-appengine/issues/detail?id=198">bug</a> that causes aliased fields to be written to the datastore with incorrect names. Make sure the names of your aliased fields don't end in "Key" or else your join won't return any results. In the above example you could trigger this bug by renaming Student.dorm to Student.dormKey.<br />
<br />
JDO:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@PersistenceCapable(detachable = "true")</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class Student {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @PrimaryKey</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Long id;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private String year;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Element(dependent = "true")</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private List<Key> courses = new ArrayList<Key>();</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Persistent</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Dorm dorm;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@PersistenceCapable(detachable = "true")</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class Course {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @PrimaryKey</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Key key;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private String department;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">@PersistenceCapable(detachable = "true")</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public class Dorm {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @PrimaryKey</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private Key key;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"></span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> private String campus;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
---------------------------------------<br />
Here's a query that returns all Juniors enrolled in a Biology course:<br />
JPA:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Query q = em.createQuery("select from " + Student.class.getName() </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + " s JOIN s.coursesAlias c where "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "c.department = 'Biology' and "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "s.year = 'Junior'");</span><br />
<br />
JDO:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Query q = pm.newQuery("select from " + Student.class.getName() </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + " where "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "courses.contains(c) && c.department == 'Biology' && "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "year == 'Junior'");</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">q.declareVariables(Course.class.getName() + " c");</span><br />
<br />
It's definitely strange to be referring to non-existent properties of a Key field and a List<Key> field, but since proper unowned relationships are not yet supported I eased up some of the JDOQL compiler's type checking to let this go through (sorry Andy, I know you hate this). Non-standard!<br />
---------------------------------------<br />
<br />
And here's a query that returns all Juniors who live in a dorm on the North campus of the school:<br />
JPA:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Query q = em.createQuery("select from " + Student.class.getName() </span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + " s JOIN s.dormAlias d where "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "d.campus = 'North' and "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "s.year = 'Junior'");</span><br />
<br />
JDO:<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">Query q = pm.newQuery("select from " + Student.class.getName() + " where "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "dorm == d && d.campus == 'North' && "</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> + "year == 'Junior'");</span><br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">q.declareVariables(Dorm.class.getName() + " d");</span></div>Again, I'm taking some liberties with standard JDOQL here.<br />
---------------------------------------<br />
These examples should be enough to get you started. Currently, only 2-way joins are supported but there's no reason it can't be extended to n-way. As for outer joins, arbitrary sorts, and inequality filters, my colleagues and I are still thinking hard about it. <br />
<br />
Stay tuned for more posts in which I'll explain how to make this work for owned relationships and the underlying algorithm involved. Give simple joins a try and let me know how it goes!Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com34tag:blogger.com,1999:blog-4143308268271113602.post-89430841637733198442010-01-15T21:20:00.000-08:002010-01-15T21:34:51.867-08:00Querying with Key parameters<span style="font-family: 'Courier New', Courier, monospace;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: Times, 'Times New Roman', serif;">Today we're going to cover a small 'gotcha' that I've seen on the forums a number of times: How do you execute a query where one or more of the parameters are of type com.google.appengine.datastore.api.Key?</span><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: Times, 'Times New Roman', serif;"></span><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: Times, 'Times New Roman', serif;">Did you know that a Key has two different string representations? You can see the first by calling Key.toString():</span><br />
<span style="font-family: Times, 'Times New Roman', serif;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">Key key = KeyFactory.createKey("Foo", 25);</span><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">System.out.println(key); </span><span style="font-family: 'Courier New', Courier, monospace;">// prints </span><span style="font-family: 'Courier New', Courier, monospace;">Foo(25)</span><br />
</div></div></span><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: Times, 'Times New Roman', serif;">You can see the second by calling KeyFactory.keyToString():</span><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: Times, 'Times New Roman', serif;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">Key key = KeyFactory.createKey("Foo", 25);</span><br />
</div></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">System.out.println(KeyFactory.keyToString(key)); // prints</span><span style="font-family: 'Courier New', Courier, monospace;"> </span><span style="font-family: 'Courier New', Courier, monospace;">agR0ZXN0cgkLEgNGb28YGQw</span><br />
</div></div></span><br />
<span style="font-family: Times, 'Times New Roman', serif;">The first string is human readable, just like the </span><a href="http://java.sun.com/javase/6/docs/api/java/lang/Object.html#toString()"><span style="font-family: Times, 'Times New Roman', serif;">javadoc for Object.toString()</span></a><span style="font-family: Times, 'Times New Roman', serif;"> recommends. The second string is websafe (no escaping required) and easily parsed by a machine (it's just the Key object translated to a protocol buffer and then base-64 encoded). Seems reasonable, right? Key.toString() outputs a string that is useful in logging and debugging, KeyFactory.keyToString() outputs a string that you can safely pass around in forms and urls. Plus for those of you who, like me, care about symmetry in your apis, KeyFactory.keyToString() and KeyFactory.stringToKey() are symmetrical. If Key.toString() returned the websafe version, what would the opposite transform look like? Probably a static method on Key called fromString(), right? So now you have mirror methods but one requires an instance and the other is static. I don't like it but I digress. You're probably reading this because you'd like to learn something useful so let's move along.</span><br />
</div></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: Times, 'Times New Roman', serif;"></span><br />
</div></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: Times, 'Times New Roman', serif;">The problem with having two string representations of the Key class stems from the fact that for a number of data types it's easy to "inline" the parameter value. Take this query for example:</span><span style="font-family: Times, 'Times New Roman', serif;"><br />
JPA:</span><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: Times;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">public List</span><span style="font-family: 'Courier New', Courier, monospace;"><Flight></span><span style="font-family: 'Courier New', Courier, monospace;"> flightsByAircraftType(EntityManager em, String aircraftType) {</span><br />
</div></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;"> String queryStr = String.format("select from " + Flight.class.getName() + </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> " where aircraftType = '%s'", aircraftType);</span><br />
</div></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;"> return em.createQuery(queryStr).getResultList();</span><br />
</div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">}</span><br />
</div></div></div></span><br />
<span style="font-family: Times, 'Times New Roman', serif;">JDO:</span><br />
</div></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: Times, 'Times New Roman', serif;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">public List</span><span style="font-family: 'Courier New', Courier, monospace;"><Flight></span><span style="font-family: 'Courier New', Courier, monospace;"> flightsByAircraftType(PersistenceManager pm, String aircraftType) {</span><br />
</div></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;"> String queryStr = String.format("select from " + Flight.class.getName() + </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> " where aircraftType == '%s'", aircraftType);</span><br />
</div></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;"> return (List</span><span style="font-family: 'Courier New', Courier, monospace;"><Flight></span><span style="font-family: 'Courier New', Courier, monospace;">) pm.newQuery(queryStr).execute();<br />
}</span><br />
</div></div></span><br />
<span style="font-family: Times, 'Times New Roman', serif;">Concise, readable, and correct. But now let's do the same thing with a Key parameter:<br />
JPA:</span><br />
<span style="font-family: Times, 'Times New Roman', serif;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">public List</span><span style="font-family: 'Courier New', Courier, monospace;"><Flight></span><span style="font-family: 'Courier New', Courier, monospace;"> flightPaginationByDest(EntityManager em, Key dest1, Key dest2) {</span><br />
</div></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;"> String queryStr = String.format("select from " + Flight.class.getName() + </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> " where dest > '%s' and dest <= '%s'", dest1, dest2);</span><br />
</div></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;"> return em.createQuery(queryStr).getResultList();</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">}</span><br />
</div></div></span><br />
<span style="font-family: Times, 'Times New Roman', serif;">JDO:</span><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: Times;"><div><span style="font-family: 'Courier New', Courier, monospace;"><span style="font-family: Times;"><span style="font-family: 'Courier New', Courier, monospace;">public List</span><span style="font-family: 'Courier New', Courier, monospace;"><Flight></span><span style="font-family: 'Courier New', Courier, monospace;"> flightPaginationByDest(PersistenceManager pm, Key dest1, Key dest2) {</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> String queryStr = String.format("select from " + Flight.class.getName() + </span></span></span><br />
<span style="font-family: 'Courier New', Courier, monospace;"><span style="font-family: Times;"><span style="font-family: 'Courier New', Courier, monospace;"> " where dest > '%s' && dest <= '%s'", dest1, dest2);</span></span></span><br />
<span style="font-family: 'Courier New', Courier, monospace;"><span style="font-family: Times;"> <span style="font-family: 'Courier New', Courier, monospace;"> return (List</span><span style="font-family: 'Courier New', Courier, monospace;"><Flight></span><span style="font-family: 'Courier New', Courier, monospace;">) pm.newQuery(queryStr).execute();</span></span></span><br />
<span style="font-family: 'Courier New', Courier, monospace;"><span style="font-family: Times;"><span style="font-family: 'Courier New', Courier, monospace;">}</span><br />
</span></span><br />
</div><div></div></span><span style="font-family: Times, 'Times New Roman', serif;">Concise, readable, but incorrect. We just asked the datastore for all Flights departing from airports between "Airport(15)" and "Airport(30)", and that's not going to match anything, ever, because we passed a String parameter to a query on entities where every single "dest" property is of type Key. Remember, the datastore is schema-less. If we were using the low-level api you could create some flight entities where the "dest" property is of type String and some flight entities where the "dest" property is of type Key. The datastore has no problem with that.</span><span style="font-family: Times, 'Times New Roman', serif;"><br />
</span><span style="font-family: Times, 'Times New Roman', serif;">So what's the solution? Ultimately the query compiler should detect this, and in future releases you can expect to get an exception if you attempt to match an unencoded String value against a Key. Until this is implemented I'd recommend using parameters:</span><span style="font-family: Times, 'Times New Roman', serif;"><br />
</span><span style="font-family: Times, 'Times New Roman', serif;">JPA:</span><br />
<span style="font-family: Times, 'Times New Roman', serif;"><div><span style="font-family: 'Courier New', Courier, monospace;"><span style="font-family: Times;"><span style="font-family: 'Courier New', Courier, monospace;"><span style="font-family: Times;"><span style="font-family: 'Courier New', Courier, monospace;">public List<Flight> flightPaginationByDest(EntityManager em, Key dest1, Key dest2) {</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> String queryStr = "select from " + Flight.class.getName() + </span></span></span></span></span><br />
<span style="font-family: 'Courier New', Courier, monospace;"><span style="font-family: Times;"><span style="font-family: 'Courier New', Courier, monospace;"><span style="font-family: Times;"><span style="font-family: 'Courier New', Courier, monospace;"> " where dest > :p1 and dest <= :p2";</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> Query q = em.createQuery(queryStr);</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> q.setParameter("p1", dest1);</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> q.setParameter("p2", dest2);</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> return q.getResultList();</span></span></span></span></span><br />
<span style="font-family: 'Courier New', Courier, monospace;"><span style="font-family: Times;"><span style="font-family: 'Courier New', Courier, monospace;"><span style="font-family: Times;"><span style="font-family: 'Courier New', Courier, monospace;">}</span></span></span></span></span><br />
</div></span><br />
<span style="font-family: Times, 'Times New Roman', serif;">JDO:<br />
<span style="font-family: 'Courier New', Courier, monospace;">public List<Flight> flightPaginationByDest(PersistenceManager pm, Key dest1, Key dest2) {</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> String queryStr = "select from " + Flight.class.getName() + </span></span><br />
<span style="font-family: Times, 'Times New Roman', serif;"><span style="font-family: 'Courier New', Courier, monospace;"> " where dest > :p1 && dest <= :p2";</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> return (List<Flight>) pm.newQuery(queryStr).execute(dest1, dest2);</span><br />
<span style="font-family: 'Courier New', Courier, monospace;">}</span></span><br />
</div></span>Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com23tag:blogger.com,1999:blog-4143308268271113602.post-83685767744690495222009-12-30T13:25:00.000-08:002009-12-30T13:25:21.059-08:00More compact @PersistenceCapable declarationsWhen I started writing I said I was going to treat JDO and JPA equally in these posts, but I've just learned something so earth-shatteringly helpful that happens to be specific to JDO. What would you have me do? Should I withhold valuable information just because it doesn't have a corresponding representation in JPA? That hardly seems right. How about a compromise: I'm going to share the JDO nugget and I promise to make it up to the JPA users at some point in the future. Sound reasonable? We have a deal? Ok, good. Now hold on to your hats, here we go:<br />
<br />
I've always written PersistenceCapable declarations like this:<br />
<br />
<span style="font-family: 'Courier New', Courier, monospace;">@PersistenceCapable(identityType = IdentityType.APPLICATION)</span><br />
<span style="font-family: 'Courier New', Courier, monospace;">public class Foo {</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> @PrimaryKey</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> private Key key;</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> // ...</span><br />
<span style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<br />
Kinda verbose, right? It turns out that if you omit 'identityType' DataNucleus will still do the right thing so long as you've set your PrimaryKey up properly:<br />
<br />
<span style="font-family: 'Courier New', Courier, monospace;">@PersistenceCapable</span><br />
<span style="font-family: 'Courier New', Courier, monospace;">public class Foo {</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> @PrimaryKey</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> private Key key;</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> // ...</span><br />
<span style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<br />
I just saved you 41 characters. Happy new year everyone!Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com9tag:blogger.com,1999:blog-4143308268271113602.post-6063818620747598572009-12-04T10:18:00.000-08:002009-12-07T10:03:21.334-08:00Queries with != and IN filters<div>Since we launched Google App Engine for Java back in April there has been a sizable disparity between the persistence features of Python and Java: Python had support for the != and IN operators in query filters and Java did not. With the release of SDK 1.2.8 this disparity is now gone (hooray!), but before you go rushing out to take advantage of these new operators I want to take some time to explain how the performance of queries that use these operators is a little bit different from what you're used to.<br />
</div><div><br />
</div><div>The key difference is that the datastore does not natively support != and IN. Instead, these operators are implemented in "userland." The code lives in appengine-api.jar, and it knows how to transform queries that use != and IN into queries that the datastore natively supports. Let's look at an example. Suppose you want to issue a query that returns all people not named Max, ordered by name and age.<br />
</div><div><br />
</div><div>Under the hood, this query gets broken up into two queries. The first query returns all people whose name is lexicographically smaller than Max, ordered by name and age. The second query returns all people whose name is lexicographically larger than Max, ordered by name and age.<br />
</div><div><br />
</div><div><b>JPA:</b><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name <> "Max" order by name, age</span><br />
</div><div><br />
</div><div>is translated into<br />
</div><div><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" order by name, age</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" order by name, age</span><br />
</div><div><br />
</div><div><b>JDO:</b><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name != "Max" order by name, age</span><br />
</div><div><br />
</div><div>is translated into

<br />
</div><div><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" order by name, age </span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" order by name, age</span><br />
</div><div><br />
</div><div>The results of these two queries are then merged, with the sort applied in-memory as we go. Both result sets are ordered by the same properties, so we can determine the next result to return by comparing the two results at the front of the two result sets. This makes the in-memory sort efficient. We also need to de-dupe as we go because a multi-value property may place an Entity in both result sets. The performance of this query is equivalent to the performance of the two underlying queries with some additional memory consumption because we need to maintain references to all the results we've already returned in order to de-dupe.<br />
</div><div><br />
</div><div>IN filters are implemented in similar fashion, but instead of requiring a fixed number of queries to fulfill they require N queries, where N is the number of values in the IN clause. Suppose you want all people whose favorite foods are cheeseburgers, pizza, and fried chicken, ordered by favoriteFood and age.<br />
</div><div><br />
</div><div>Under the hood this query gets broken up into three queries. The first query returns all people whose favorite food is cheeseburgers, ordered by favoriteFood and age. The second query returns all people whose favorite food is pizza, ordered by favoriteFood and age. The third query includes all people whose favorite food is fried chicken, ordered by favoriteFood and age.<br />
</div><div><br />
</div><div><b>JPA:</b><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> favoriteFood IN ('cheeseburger', 'pizza', 'fried chicken')</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> order by favoriteFood, age</span><br />
</div><div><br />
</div><div>is translated into<br />
</div><div><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where favoriteFood = "cheeseburger" order by favoriteFood, age</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where favoriteFood = "pizza" order by favoriteFood, age</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where favoriteFood = "fried chicken" order by favoriteFood, age</span><br />
</div><div><br />
</div><div><b>JDO:</b><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">Query q = pm.newQuery(</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> "select from Person where :p1.contains(favoriteFood) order by favoriteFood, age");</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">q.execute(Arrays.asList("cheeseburger", "pizza", "fried chicken"));</span><br />
</div><div><br />
</div><div>is translated into<br />
</div><div><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where favoriteFood == "cheeseburger" order by favoriteFood, age</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where favoriteFood == "pizza" order by favoriteFood, age</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where favoriteFood == "fried chicken" order by favoriteFood, age</span><br />
</div><div><br />
</div><div>Once again we merge the results of these three queries, sorting and de-duping as we go. Once again the performance of this query is equivalent to the performance of the underlying queries with some additional memory consumption.<br />
</div><div><br />
</div><div>Can you issue a query that combines != and IN? Absolutely. Can you have multiple IN filters? Absolutely again! But, be careful, if your query requires more than 30 underlying queries to fulfill you'll get an exception. Here's a query that requires too many underlying queries to fulfill:<br />
</div><div><br />
</div><div><b>JPA:</b><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> name <> "Max" AND </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> favoriteFood IN ("cheeseburger", "pizza", "fried chicken") AND</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> age IN (10, 11, 12, 13, 14, 15, 16, 17, 18 19) order by name</span><br />
</div><div><br />
</div><div>is translated into<br />
</div><div><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "cheeseburger" AND age = 10 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "cheeseburger" AND age = 10 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "cheeseburger" AND age = 11 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "cheeseburger" AND age = 11 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "cheeseburger" AND age = 12 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "cheeseburger" AND age = 12 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "cheeseburger" AND age = 13 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "cheeseburger" AND age = 13 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "cheeseburger" AND age = 14 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "cheeseburger" AND age = 14 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "cheeseburger" AND age = 15 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "cheeseburger" AND age = 15 order by name</span><br />
</div><div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "cheeseburger" AND age = 16 order by name</span><br />
</div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "cheeseburger" AND age = 16 order by name</span><br />
</div></div><div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "cheeseburger" AND age = 17 order by name</span><br />
</div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "cheeseburger" AND age = 17 order by name</span><br />
</div></div><div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "cheeseburger" AND age = 18 order by name</span><br />
</div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "cheeseburger" AND age = 18 order by name</span><br />
</div></div><div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "cheeseburger" AND age = 19 order by name</span><br />
</div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "cheeseburger" AND age = 19 order by name</span><br />
</div></div><div></div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "pizza" AND age = 10 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "pizza" AND age = 10 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" AND favoriteFood = "pizza" AND age = 11 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" AND favoriteFood = "pizza" AND age = 11 order by name</span><br />
</div><div><br />
</div><div>.... and so on.<br />
</div><div><br />
</div><div><b>JDO:</b><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">Query q = pm.newQuery("select from Person where " + </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> "name != 'Max' AND " +</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> ":p1.contains(favoriteFood) && :p2.contains(age) order by name");</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">q.execute(Arrays.asList("cheeseburger", "pizza", "fried chicken"),</span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> Arrays.asList(10, 11, 12, 13, 14, 15, 16, 17, 18, 19));</span><br />
</div><div><br />
</div><div>is translated into<br />
</div><div><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "cheeseburger" AND age == 10 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "cheeseburger" AND age == 10 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "cheeseburger" AND age == 11 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "cheeseburger" AND age == 11 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "cheeseburger" AND age == 12 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "cheeseburger" AND age == 12 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "cheeseburger" AND age == 13 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "cheeseburger" AND age == 13 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "cheeseburger" AND age == 14 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "cheeseburger" AND age == 14 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "cheeseburger" AND age == 15 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "cheeseburger" AND age == 15 order by name</span><br />
</div><div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "cheeseburger" AND age == 16 order by name</span><br />
</div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "cheeseburger" AND age == 16 order by name</span><br />
</div></div><div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "cheeseburger" AND age == 17 order by name</span><br />
</div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "cheeseburger" AND age == 17 order by name</span><br />
</div></div><div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "cheeseburger" AND age == 18 order by name</span><br />
</div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "cheeseburger" AND age == 18 order by name</span><br />
</div></div><div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "cheeseburger" AND age == 19 order by name</span><br />
</div></div><div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "cheeseburger" AND age == 19 order by name</span><br />
</div></div><div></div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "pizza" AND age == 10 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "pizza" AND age == 10 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name < "Max" && favoriteFood == "pizza" AND age == 11 order by name</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where name > "Max" && favoriteFood == "pizza" AND age == 11 order by name</span><br />
</div><div><br />
</div><div>.... and so on.<br />
</div><div><br />
</div><div>One final note that isn't entirely related but that I'll tack on anyway: You can now use the 'OR' operator in your queries as long as the query can be rewritten using IN. That means this now works:<br />
</div><div><br />
</div><div><b>JPA:</b><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> favoriteFood = "cheeseburger" OR </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> favoriteFood = "pizza" OR </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> favoriteFood = "fried chicken"</span><br />
</div><div><br />
</div><div><b>JDO:</b><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> favoriteFood == "cheeseburger" || </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> favoriteFood == "pizza" || </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> favoriteFood == "fried chicken"</span><br />
</div><div><br />
</div><div>But this still doesn't:<br />
</div><div><br />
</div><div><b>JPA:</b><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> favoriteFood = "cheeseburger" OR age = 10</span><br />
</div><div><br />
</div><div><b>JDO:</b><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">select from Person where </span><br />
<span style="font-family: 'Courier New', Courier, monospace;"> favoriteFood == "cheeseburger" || age == 10</span><br />
</div><div><br />
</div><div>Enjoy the new operators!<br />
</div><div><br />
</div><span style="font-family: 'Lucida Grande'; font-size: small;"><span style="font-size: 11px;"></span></span>Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com38tag:blogger.com,1999:blog-4143308268271113602.post-15520995516469673602009-11-24T16:09:00.000-08:002009-11-24T16:09:33.382-08:00Prerelease Of The Next SDKHi all,<br />
<br />
Just a quick post to let you know that we've made a prerelease of SDK 1.2.8 available at http://code.google.com/p/googleappengine/downloads/list. This is for local development only, but should still give you an opportunity to try out our new features and let us know if anything looks amiss. Persistence-related features of note:<br />
- Support for inheritance in JDO and JPA (finally!)<br />
- Support for != filters in the low level api, JDO, and JPA<br />
- Support for IN filters in the low level api, JDO, and JPA<br />
<br />
All the gory details can be found in the release notes of the download.<br />
<br />
Please give it a try!Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com73tag:blogger.com,1999:blog-4143308268271113602.post-79801288308340400242009-11-20T13:16:00.000-08:002009-11-20T13:21:15.947-08:00Case Insensitive QueriesHi everyone. I've been posting a bit less frequently the past few weeks because the team has really been working hard on our next release. It's almost ready.....we're excited to get it out to you! On to today's topic....<br />
<br />
I see questions about case insensitive queries on our <a href="http://groups.google.com/group/google-appengine-java/search?group=google-appengine-java&q=case+insensitive+queries&qt_g=Search+this+group">message board</a> pretty regularly. We usually give people the same answer, but rather than continuing to describe the solution, why not just provide it? Here we go.<br />
<br />
If you're using a relational database it's pretty easy to execute case-insensitive queries because it's a native feature of the database. It usually looks something like this:<br />
<br />
select * from Person where UPPER(lastName) = 'ROSS'<br />
<br />
This is a challenge for the App Engine Datastore because we rely on executing scans over a limited subset of your data and returning results as we encounter them. Consider the following strings in lexicographic order:<br />
... <br />
ROSE<br />
...<br />
ROSS<br />
... <br />
ROSTA <br />
...<br />
Rose<br />
...<br />
Ross<br />
...<br />
Rosta<br />
...<br />
rose<br />
...<br />
ross<br />
...<br />
rosta<br />
...<br />
<br />
As you can see there could be an unlimited number of rows in between 'ROSS' and 'Ross', and also between 'Ross' and 'ross' (okay not unlimited, we do have a limit on indexed string length, but definitely Large), so if we start scanning at 'ROSS' we might have to skip a huge number of results before we hit 'Ross', and that doesn't scale. This is why the datastore doesn't support case-insensitive queries.<br />
<br />
Fortunately it's not hard to implement support for case-insensitive queries in your application. Here's the approach: for each field that you want to query in a case-insensitive way, create a duplicate field that stores the value of that field in either all upper or lowercase letters. Then, have your model object register a pre-persist callback with your persistence framework (JPA or JDO), <span style="font-family: inherit;">and then populate the duplicate fields inside that callback. Here's what it looks like.</span><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"></span></span></span></span><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><b>JPA:</b></span></span></span></span><br />
<span style="font-family: "Courier New",Courier,monospace;">import javax.persistence.PrePersist;</span><br />
<span style="font-family: "Courier New",Courier,monospace;">import javax.persistence.PreUpdate;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"> <br />
</span></span></span></span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;">@Entity</span></span></span></span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"></span><span style="font-family: "Courier New",Courier,monospace;">public class Person {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> @Id</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> @GeneratedValue(strategy = GenerationType.IDENTITY)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private Long id;</span></span></span></span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"> private String lastName;</span><span style="font-family: "Courier New",Courier,monospace;"> </span></span></span></span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"> private String uppercaseLastName;</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> public Long getId() {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> return id;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span></span></span></span></span><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"> public String getLastName() {</span></span></span></span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"> </span><span style="font-family: "Courier New",Courier,monospace;"> return lastName;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> public void setLastName(String lastName) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> this.lastName = lastName;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> @PrePersist</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"></span></span></span></span></span><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"> @PreUpdate</span><br style="font-family: "Courier New",Courier,monospace;" /> <span style="font-family: "Courier New",Courier,monospace;"></span></span></span></span></span><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"> public void prePersist() {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (lastName != null) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> uppercaseLastName = lastName.toUpperCase();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } else {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> uppercaseLastName = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><span style="font-family: "Courier New",Courier,monospace;"> </span></span></span></span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;">}</span></span></span></span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"> </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">public List<Person> getPeopleByLastName(String lastName) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Query q = em.createQuery("select from Person where uppercaseLastName = :p");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> q.setParameter("p", lastName.toUpperCase());</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> return (List<Person>) q.getResultList();</span><span style="font-family: "Courier New",Courier,monospace;"> </span></span></span></span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;">}</span><br style="font-family: "Courier New",Courier,monospace;" /><b><br />
</b></span></span></span></span><br />
<span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-family: inherit;"><b>JDO</b><b>:</b><br />
</span></span></span></span><div style="font-family: "Courier New",Courier,monospace;">import javax.jdo.listener.StoreCallback;<br />
<br />
@PersistenceCapable(identityType = IdentityType.APPLICATION)<br />
public class Person implements StoreCallback {<br />
@PrimaryKey<br />
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)<br />
private Long id;<br />
private String lastName;<br />
private String uppercaseLastName;<br />
<br />
public Long getId() {<br />
return id;<br />
}<br />
<br />
public String getLastName() {<br />
return lastName;<br />
}<br />
<br />
public void setLastName(String lastName) {<br />
this.lastName = lastName;<br />
}<br />
</div><span style="font-family: inherit;"><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> public void jdoPreStore() {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (lastName != null) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> uppercaseLastName = lastName.toUpperCase();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } else {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> uppercaseLastName = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">}</span><br />
</span><br />
<span style="font-family: "Courier New",Courier,monospace;">public List<Person> getPeopleByLastName(String lastName) {</span><span style="font-family: "Courier New",Courier,monospace;"> </span><br />
<span style="font-family: "Courier New",Courier,monospace;"> Query q = pm.newQuery(Person.class, "uppercaseLastName == :p");</span><span style="font-family: "Courier New",Courier,monospace;"> </span><br />
<span style="font-family: "Courier New",Courier,monospace;"> return (List<Person>) q.execute(lastName.toUpperCase());</span><span style="font-family: "Courier New",Courier,monospace;"> </span><br />
<span style="font-family: "Courier New",Courier,monospace;">}</span><br />
<br />
<div style="font-family: inherit;">Note that in both examples I have not created a getter or a setter for the <span style="font-family: "Courier New",Courier,monospace;">uppercaseLastName</span> field. That's because this field is part of the interface we're exposing to the persistence framework but not part of the public interface of the model object. Your query code can filter by the <span style="font-family: "Courier New",Courier,monospace;">uppercaseLastName</span> field but the client code that interacts with the Person object has no reason to know that there are duplicate fields that are used to support case-insensitive queries. <br />
</div>Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com49tag:blogger.com,1999:blog-4143308268271113602.post-51199125734261719422009-11-04T15:36:00.000-08:002009-11-04T15:37:48.073-08:00Unindexed Properties<div style="font-family: inherit;">Did you know that, by default, the App Engine Datastore writes two index records for every entity property that isn't a com.google.appengine.api.datastore.Blob or a com.google.appengine.api.datastore.Text<span style="font-family: inherit;">?<span style="font-size: small;"> </span></span><span style="font-family: inherit;"><span style="font-family: inherit;"><span style="font-size: small;">It's true! These index records allow you to execute a variety of queries involving the property without creating a composite index.</span></span></span><br />
</div><div style="font-family: inherit;"><br />
</div><div style="font-family: inherit;"><span style="font-family: inherit;">Now, these index records don't come for free. They take time to write and they take up space on disk. If you have a property that you're absolutely positively sure you'll never want to filter or sort by, you can opt-out of the default indexing that is going on. Let's look at an example. We'll use a Book class with a </span>com.google.appengine.api.datastore.Link property that is the URL of an image of the Book's cover. Our assumption here is that we don't need to select Books based on the value of this property and we don't need to sort them based on the value of this property.<br />
<br />
</div><div style="font-family: inherit;"><br />
</div><div style="font-family: inherit;">JPA:<br />
</div><div style="font-family: "Courier New",Courier,monospace;">@Entity<br />
public class Book {<br />
@Id<br />
@GeneratedValue(strategy=GenerationType.IDENTITY)<br />
private Long id;<br />
</div><div style="font-family: "Courier New",Courier,monospace;"><br />
@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")<br />
private Link coverImageUrl;<br />
<br />
private String title;<br />
</div><div style="font-family: "Courier New",Courier,monospace;"><br />
</div><div style="font-family: "Courier New",Courier,monospace;"> // getters and setters<br />
}<br />
</div><span style="font-family: "Courier New",Courier,monospace;"><br />
</span><br />
<div style="font-family: inherit;">JDO:<br />
</div><div style="font-family: "Courier New",Courier,monospace;">@PersistenceCapable(identityType=IdentityType.APPLICATION)<br />
public class Book {<br />
@PrimaryKey<br />
@Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)<br />
private Long id;<br />
<br />
@Persistent<br />
@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")<br />
private Link coverImageUrl;<br />
</div><div style="font-family: "Courier New",Courier,monospace;"><br />
</div><div style="font-family: "Courier New",Courier,monospace;"> private String title;<br />
</div><div style="font-family: "Courier New",Courier,monospace;"><br />
</div><div style="font-family: "Courier New",Courier,monospace;"> // getters and setters<br />
}<br />
</div><div style="font-family: "Courier New",Courier,monospace;"><br />
</div><div style="font-family: "Courier New",Courier,monospace;"><br />
</div><div style="font-family: inherit;">In both examples, the "gae.unindexed" extension tells App Engine that you want to opt out of the default indexing. Now remember, requirements change over time. Just because you're not filtering or sorting by a property today doesn't mean you won't filter or sort by that property tomorrow, so think hard before you choose to mark something as 'unindexed.'<br />
</div><div style="font-family: inherit;"><br />
</div><div style="font-family: inherit;">Of course, no matter how well you've planned you'll find yourself in a situation where you have existing data and you need to change a property from indexed to unindexed or from unindexed to indexed. What do you do?<br />
</div><div style="font-family: inherit;">Let's start with the easy one - going from indexed to unindexed.<br />
</div><div style="font-family: inherit;"><br />
</div><div style="font-family: inherit;">If you change a property from indexed to unindexed, the index records for all existing entities with that property will continue to exist until you update (or delete) the entity with which those index records are associated. Any new entities will be created without the index records for the newly unindexed property. As long as you've taken care to purge your code of all queries that filter or sort by the property that is now unindexed you'll be ready to go as soon as you upload your new application version.<br />
<br />
</div><div style="font-family: inherit;">Changing a property from unindexed to indexed is more difficult. Presumably you're doing this because you need to filter or sort by that property in your queries, and since entities without index records for the filter and sort properties are automatically excluded from the result set, the only way that your queries involving the newly indexed property are going to return the results you expect is if you create index records for all the entities that existed before you made the property indexed. Yikes. I told you to think hard, didn't I?<br />
</div><div style="font-family: inherit;"><br />
</div><div style="font-family: inherit;">So what do you do? Well, the missing index records will be created whenever you rewrite an entity, so you'll need to map over all entities of the appropriate kind and "touch" each one (fetch it then put it). You can use the Task Queue to break this work up, and when Datastore Cursors are released (coming soon!) this will be even easier. Still, you've got some work to do.<br />
</div><div style="font-family: inherit;"><br />
</div><div style="font-family: inherit;">My final warning: Unindexed properties are an optimization, and just like any optimization it is possible to invoke it prematurely. Using unindexed properties will speed up writes and reduce disk usage, but will it speed up writes and reduce disk usage enough to make the optimization worthwhile? I can't answer that question for you. The impact will depend on how much data you have and how often you're writing it.<br />
</div>Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com315tag:blogger.com,1999:blog-4143308268271113602.post-48140548171885923972009-10-21T10:44:00.000-07:002009-10-21T11:16:13.500-07:00Optimistic Locking With @VersionThe datastore does optimistic locking, so if you update an entity inside a transaction and another request commits an update to either that same entity or some other entity in the same entity group before you commit your transaction, you will get an exception. This keeps your data from getting stomped on when there are concurrent requests. However, in the web-app world where clients can and typically do maintain state across requests, there is a far more likely scenario in which your data can get stomped on. Consider the following scenario involving a bug tracking system:<br />
<br />
Time 1: User A brings up the details page for Bug 838.<br />
Time 2: User B brings up the details page for Bug 838.<br />
Time 3: User A assigns Bug 838 to Jim.<br />
Time 4: User B assigns Bug 838 to Sally.<br />
<br />
Even though User A and User B updated Bug 838 at different times, User A's update was effectively stomped on by User B. Sure, the fact that Bug 838 was assigned to Jim at some point may be in the history for the Bug, we can't change the fact that User B made a decision to assign Bug 838 based on out-of-date information. In a bug tracking system this might not be such a big deal, but if you're doing something like compensation planning you'd much rather have your users receive an exception when they make an update based on out-of-date information. Fortunately this is easy to implement using JPA/JDO on top of App Engine. Let's use a Person object with a 'salary' property as an example.<br />
<br />
<b>JPA:</b><br />
<span style="font-family: "Courier New",Courier,monospace;">@Entity</span><br />
<span style="font-family: "Courier New",Courier,monospace;">public class Person {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> @Id</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> @GeneratedValue(strategy=GenerationType.IDENTITY)</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> private Long id;</span><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"> private int salary;</span><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"> @Version</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> private long version;</span><br />
<br />
<div style="font-family: "Courier New",Courier,monospace;"> // ...getters and setters<br />
</div><span style="font-family: "Courier New",Courier,monospace;">}</span><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">public void updateSalary(EntityManager em, Person p, int newSalary) {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> em.getTransaction().begin();</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> try {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> p.setSalary(newSalary);</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> p = em.merge(p);</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> em.getTransaction().commit();</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> } catch (RollbackException e) {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> if (e.getCause() instanceof OptimisticLockException) {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> handleVersionConflict(e.getCause(), p);</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> } else {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> throw e;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> }</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> if (em.getTransaction().isActive()) {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> em.getTransaction().rollback();</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> }</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> }</span><br />
<span style="font-family: "Courier New",Courier,monospace;">}</span><br />
<br />
<b>JDO:</b><span style="font-family: "Courier New",Courier,monospace;"> <br />
</span><br />
<span style="font-family: "Courier New",Courier,monospace;">@PersistenceCapable(identityType=IdentityType.APPLICATION)<br />
@Version(strategy=VersionStrategy.VERSION_NUMBER)<br />
public class Person {<br />
@PrimaryKey<br />
@Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)<br />
private Long id;<br />
<br />
private int salary;</span><br />
<span style="font-family: "Courier New",Courier,monospace;"></span><br />
<span style="font-family: "Courier New",Courier,monospace;"> // ...getters and setters</span><br />
<span style="font-family: "Courier New",Courier,monospace;">}</span><br />
<span style="font-family: "Courier New",Courier,monospace;"></span><br />
<span style="font-family: "Courier New",Courier,monospace;">public void updateSalary(PersistenceManager pm, Person p, int newSalary) {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> pm.currentTransaction().begin();</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> try {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> p.setSalary(newSalary);</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> pm.makePersistent(p);</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> pm.currentTransaction().commit();</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> } catch (JDOOptimisticVerificationException e) {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> handleVersionConflict(e, p);</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><span style="font-family: "Courier New",Courier,monospace;"> </span><br />
<span style="font-family: "Courier New",Courier,monospace;"> if (pm.currentTransaction().isActive()) {</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> pm.currentTransaction().rollback();</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> }</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> }</span><br />
<span style="font-family: "Courier New",Courier,monospace;">}</span><br />
<br />
If you declare a Version field on your model object, JDO/JPA will compare the value of that field on the instance you are updating with the value of that field in the datastore. If the version numbers are equal the version number will be incremented and your model object will be persisted. If the version numbers are not equal the appropriate exception will be thrown. In the above examples I've caught this exception in the update code itself, but this is really just to illustrate what's going on. In practice I would most likely let this exception propagate out of the update method and handle it at a higher level, perhaps even as part of a generic exception handler in my presentation code.<br />
<br />
Note that there is a performance cost to using this feature. The datastore does not support updates with predicates the way relational databases do ('update person where version = 3') so in order to perform the version comparison it needs to do an additional get() for the entity that corresponds to your model object. You'll have to decide for yourself whether or not the cost of the additional fetch is worthwhile.<br />
<br />
Now, if you've looked at both the JPA and JDO examples you may have noticed that JPA requires the declaration of an explicit field annotated with @Version while JDO accepts an @Version annotation on the class without an explicit field. If you are using JDO and would like the version stored in an explicit field you can use a DataNucleus-specific extension to tell JDO which field to use. You can see an example of this <a href="http://code.google.com/p/datanucleus-appengine/source/browse/branches/1_0_3/tests/org/datanucleus/test/HasVersionWithFieldJDO.java">here</a>.<br />
<br />
Using JPA and JDO's built-in versioning checks is an easy way to help users of your app make decisions based on up-to-date information. Try it out!Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com105tag:blogger.com,1999:blog-4143308268271113602.post-86275102294887874682009-10-15T15:59:00.000-07:002010-02-10T19:34:03.260-08:00Serialized Fields(This was <a href="http://groups.google.com/group/google-appengine-java/browse_thread/thread/747ceed8396c0ed8/10ccdd1a0c376825#10ccdd1a0c376825">originally posted</a> to the Google App Java Google Group on October 14, 2009) <br />
<br />
Suppose one of your model objects has a field that isn't a native datastore type like String or Date and isn't another model object, like the child of an owned OneToOne relationship. Oftentimes you can use an <a href="http://code.google.com/appengine/docs/java/datastore/dataclasses.html#Embedded_Classes" target="_blank" title="Embedded class">Embedded class</a> to store that field's sub-fields in the same record as the containing record, but what if an Embedded class isn't sufficient? This is where Serialized Fields come in. With JDO and JPA it's possible to store any class that implements java.io.Serializable in a single property.<br />
<br />
For our example lets model Person, ContactProfiles, ContactProfile, and PhoneNumber. A Person has exactly one ContactProfiles instance, a ContactProfiles has some number of ContactProfile instances, and a ContactProfile has some number of PhoneNumber instances. Our application is such that when we retrieve a Person from the datastore it always makes sense to load their ContactProfiles as well. We'll define ContactProfiles, ContactProfile, and PhoneNumber first since they're the same whether we're using JPA or JDO:<br />
<span style="font-family: Courier New;"><br />
public static class ContactProfiles implements Serializable {<br />
private final </span><span style="font-family: Courier New;">List<<wbr></wbr>ContactProfile></span><span style="font-family: Courier New;"><contactprofile> profiles;<br />
<br />
public ContactProfiles(List<<wbr></wbr>ContactProfile> profiles) {<br />
this.profiles = profiles;<br />
}<br />
<br />
public </contactprofile></span><span style="font-family: Courier New;">List<<wbr></wbr>ContactProfile></span><span style="font-family: Courier New;"><contactprofile><contactprofile> get() {<br />
return profiles;<br />
}<br />
}<br />
<br />
public class ContactProfile implements Serializable {<br />
private String profileName;<br />
private </contactprofile></contactprofile></span><span style="font-family: Courier New;">List<PhoneNumber></span><span style="font-family: Courier New;"><contactprofile><contactprofile><phonenumber> phoneNumbers;<br />
<br />
// getters and setters<br />
}<br />
<br />
public class PhoneNumber implements Serializable {<br />
private String type;<br />
private String number;<br />
<br />
// getters and setters<br />
}<br />
</phonenumber></contactprofile></contactprofile></span><br />
<br />
The JPA Person definition:<br />
<span style="font-family: Courier New;">@Entity</span><br />
<span style="font-family: Courier New;">public class Person {</span><br />
<span style="font-family: Courier New;"> @Id</span><br />
<span style="font-family: Courier New;"> @GeneratedValue(strategy=<wbr></wbr>GenerationType.IDENTITY)</span><br />
<span style="font-family: Courier New;"> private Key id;<br />
</span><span style="font-family: Courier New;"><br />
@Lob<br />
private ContactProfiles contactProfiles;<br />
</span><span style="font-family: Courier New;"><br />
// getters and setters<br />
}<br />
<br />
</span><br />
The JDO Person definition:<br />
<br />
<span style="font-family: Courier New;">@PersistenceCapable(<wbr></wbr>identityType = IdentityType.APPLICATION, detachable = "true")</span><br />
<span style="font-family: Courier New;">public class Person {</span><span style="font-family: Courier New;"> <br />
@PrimaryKey</span><br />
<span style="font-family: Courier New;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span style="font-family: Courier New;"> private Key id;<br />
<br />
</span><span style="font-family: Courier New;"> @Persistent(serialized = "true")<br />
private ContactProfiles contactProfiles;<br />
</span><span style="font-family: Courier New;"><br />
// getters and setters<br />
}<br />
<br />
</span><br />
In this example the App Engine JDO/JPA implementation converts the Person.contactProfiles field into a <span style="font-family: Courier New;">com.google.appengine.api.<wbr></wbr>datastore.Blob</span> using standard Java serialization. This Blob is then stored as a single property on the Person Entity. When you fetch a Person the corresponding Entity is retrieved and the Blob is deserialized in just a single datastore rpc, leaving you with a ContactProfiles instance and all its associated sub-objects.<br />
<br />
There are a few drawbacks to this approach that you should be aware of. First, even though your application understands the structure of the bytes that make up the ContactProfiles, the datastore does not. As a result you can't filter or sort on any of the properties of ContactProfile or PhoneNumber. Second, all the known gotchas associated with Java serialization apply here. If you evolve your Serializable classes make sure you do it in a backwards compatible way! Third, you might be looking at the definition of the ContactProfiles class and thinking "Why are we bothering with a ContactProfiles class when Person could just store List<ContactProfile>?" The reason is that updating a serialized field is not as simple as updating other types of fields, and this extra layer of indirection makes things a bit easier. DataNucleus has no way of "seeing" the changes you make to the internal values of a serialized field, and as a result it doesn't know when it needs to flush changes to these fields to the datastore. However, DataNucleus <i>can</i> see when the top-level serialized field reference changes. Giving Person a reference to a ContactProfiles object makes it easy for us to change ContactProfiles in a way that DataNucleus is guaranteed to recognize:<br />
<br />
<span style="font-family: Courier New;">public void addContactProfile(Person p, ContactProfile cp) {<br />
// update our serialized field<br />
p.getContactProfiles().get().<wbr></wbr>add(cp);<br />
// give the person a new ContactProfiles reference so the update is detected<br />
p.setContactProfiles(new ContactProfiles(p.<wbr></wbr>getContactProfiles().get()));<br />
}<br />
</span><br />
The last line where we set a new ContactProfiles instace back on the Person is the key and it's easy to miss, so be careful! If you forget this step your updates will be ignored and you will be sad.<br />
<br />
Despite the limitations I've listed, Serialized fields are a good way to store structured data inside the record of a containing Entity. As long as you can get by without filtering or sorting on this data and you remember that it has special update requirements, serialized fields will almost certainly come in handy at some point.</contactprofile>Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com23tag:blogger.com,1999:blog-4143308268271113602.post-89718355618263860952009-10-15T15:50:00.000-07:002009-10-15T15:50:34.202-07:00Keys Only Queries(This was <a href="http://groups.google.com/group/google-appengine-java/browse_thread/thread/1d699d0db8641dbd/8f490b74d7b5aefe#8f490b74d7b5aefe">originally posted</a> to the Google App Java Google Group on October 6, 2009)<br />
<br />
<span style="font-family: Verdana;">If you use the low-level datastore api you may have noticed </span><span class="il" style="font-family: Verdana;">that</span><span style="font-family: Verdana;"> the com.google.appengine.api.datastore.Query class has a </span><span style="font-family: Courier New;">setKeysOnly()</span><span style="font-family: Verdana;"> method. If you call this method before you execute the query the datastore will return </span><span style="font-family: Courier New;">com.google.appengine.api.datastore.Entity <span style="font-family: Verdana,sans-serif;">instances </span><span class="il" style="font-family: Verdana,sans-serif;">that</span><span style="font-family: Verdana,sans-serif;"> have their keys filled in but none of their properties. This can reduce consumption of your Datastore Data Received from API quota, especially if you've got some large entities, but, more importantly, it can also reduce consumption of your Datastore CPU Time quota</span></span><span style="font-family: Verdana,sans-serif;">. How? Well, if the fulfillment of your query requires an index or a merge join your query actually executes in two stages: First it scans the index to find the keys of the entities </span><span class="il" style="font-family: Verdana,sans-serif;">that</span><span style="font-family: Verdana,sans-serif;"> match and then it issues additional scans to retrieve the entities uniquely identified by the matching keys. If your query is keys-only we can skip </span><span class="il" style="font-family: Verdana,sans-serif;">that</span><span style="font-family: Verdana,sans-serif;"> second step entirely. </span><span class="il" style="font-family: Verdana,sans-serif;">That</span><span style="font-family: Verdana,sans-serif;"> means faster queries!</span><br style="font-family: Verdana,sans-serif;" /><br style="font-family: Verdana;" /><span style="font-family: Verdana;">Now, JPA and JDO don't know anything about keys-only queries, but they do give you the flexibility to either return your entire object or some subset of the fields on your object. If you construct this subset to only contain the primary key of your object, the App Engine implementation of JPA and JDO will use a keys-only query. Let's look at some examples:</span><br style="font-family: Verdana;" /><br style="font-family: Verdana;" /><span style="font-family: Verdana;">JPA:</span><br />
<span style="font-family: Courier New;">@Entity</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;">public class Book {</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> @Id</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> @GeneratedValue(strategy=GenerationType.IDENTITY)</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> private Key id;<br />
</span><span style="font-family: Courier New;"><br />
private Date dateOfPublication;<br />
<br />
// getters and setters</span><span style="font-family: Courier New;"><br />
}<br />
<br />
</span><br />
Now let's implement a method <span class="il">that</span> returns the Keys of all Books published betweeen 2 years (we'll assume someone else is creating and closing an EntityManager named 'em' for us):<br />
<br />
<span style="font-family: Courier New;">public List<key> keysOfBooksPublishedBetween(EntityManager em, Date from, Date to) {<br />
em.getTransaction().begin();<br />
try {<br />
Query q = em.createQuery("select id from " + Book.class.getName() <br />
+ " where dateOfPublication >= :from AND dateOfPublication <= :to");<br />
q.setParameter("from", from);<br />
q.setParameter("to", to);<br />
return (List<key>) q.getResultList();<br />
} finally {<br />
<span style="font-family: Courier New;"><span style="font-family: Courier New;"> em.getTransaction().rollback();</span><br style="font-family: Courier New;" /></span> }<br />
}<br />
<br />
</key></key></span><br />
JDO:<br />
<br />
<span style="font-family: Courier New;">@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;">public class Book {</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> <br />
@PrimaryKey</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> private Key id;<br />
<br />
private Date dateOfPublication;</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"><br />
// getters and setters<br />
}<br />
</span><br />
Now let's implement a method <span class="il">that</span> returns the Keys of all Books published betweeen 2 years (we'll assume someone else is creating and closing a PersistenceManager named 'pm' for us):<br />
<br />
<br />
<span style="font-family: Courier New;"><span style="font-family: Courier New;">public List<key> keysOfBooksPublishedBetween(PersistenceManager pm, Date from, Date to) {<br />
pm.currentTransaction().begin();<br />
try {<br />
Query q = pm.newQuery("select id from " + Book.class.getName() <br />
+ " where dateOfPublication >= :from && dateOfPublication <= :to");<br />
return (List<key>) q.execute(from, to);<br />
} finally {<br />
<span style="font-family: Courier New;"><span style="font-family: Courier New;"> pm.currentTransaction().rollback();</span><br style="font-family: Courier New;" /> </span> }<br />
}<br />
</key></key></span>--------------------------------<br />
<span style="font-family: Verdana;">Notice how we are only selecting the 'id' field from our Book class. This is crucial. If you select any other fields your query will end up fetching entire entities and the optimization will be lost.</span></span>Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com12tag:blogger.com,1999:blog-4143308268271113602.post-65549192620731492972009-10-15T14:53:00.000-07:002010-03-01T17:14:09.556-08:00Updating A Bidrectional Owned One-To-Many With A New Child(This was <a href="http://groups.google.com/group/google-appengine-java/browse_thread/thread/28d9c3b99df25e43/b43702a150c5aa1b#b43702a150c5aa1b">originally posted</a> to the Google App Java Google Group on September 28, 2009) <br />
<br />
All the way back in our first post one we demonstrated how to create both a parent and a child of a bidirectional, owned, one-to-many relationship at the same time. This week we're going to see how to add a child to an existing parent. We'll use the same model objects we used before:<br />
<br />
JPA:<br />
<span style="font-family: Courier New;">@Entity</span><br />
<span style="font-family: Courier New;">public class Book {</span><br />
<span style="font-family: Courier New;"> @Id</span><br />
<span style="font-family: Courier New;"> @GeneratedValue(strategy=<wbr></wbr>GenerationType.IDENTITY)</span><br />
<span style="font-family: Courier New;"> private Key id;<br />
</span><span style="font-family: Courier New;"><br />
private String title;</span><br />
<span style="font-family: Courier New;"><br />
@OneToMany(mappedBy = "book", cascade = CascadeType.ALL)</span><br />
<span style="font-family: Courier New;"> private List<Chapter> chapters = new ArrayList<Chapter>();</span><br />
<span style="font-family: Courier New;"><br />
// getters and setters<br />
}<br />
<br />
</span><span style="font-family: Courier New;">@Entity</span><br />
<span style="font-family: Courier New;">public class Chapter {</span><br />
<span style="font-family: Courier New;"> @Id</span><br />
<span style="font-family: Courier New;"> @GeneratedValue(strategy=<wbr></wbr>GenerationType.IDENTITY)</span><br />
<span style="font-family: Courier New;"> private Key id;</span><br />
<br />
<span style="font-family: Courier New;"> private String title;<br />
private int numPages;<br style="font-family: Courier New;" /> </span><br />
<span style="font-family: Courier New;"> @ManyToOne(fetch = FetchType.LAZY)</span><br />
<span style="font-family: Courier New;"> private Book book;<br />
<br />
</span><span style="font-family: Courier New;"> // getters and setters</span><br />
<span style="font-family: Courier New;">}</span><br />
<br />
Now let's assume we've already created a book with a few chapters in the datastore and we want to add a brand new chapter to a Book with a given id (we'll assume someone else is creating and closing an EntityManager named 'em' for us):<br />
<br />
<span style="font-family: Courier New;">public void addChapterToBook(EntityManager em, Key bookKey, Chapter chapter) {<br />
em.getTransaction().begin();<br />
<span style="font-family: Courier New;"> try {<br />
Book b = em.find(Book.class, bookKey);<br />
if (b == null) {<br />
throw new RuntimeException("Book " + bookKey + " not found!");<br />
}<br />
b.getChapters().add(chapter);</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> em.getTransaction().commit();</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;">} finally {</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> if (em.getTransaction().isActive(<wbr></wbr>)) {</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> em.getTransaction().rollback()<wbr></wbr>;</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> }</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> }</span><br />
}<br />
<br />
</span><br />
JDO:<br />
<br />
<span style="font-family: Courier New;">@PersistenceCapable(<wbr></wbr>identityType = IdentityType.APPLICATION, detachable = "true")</span><br />
<span style="font-family: Courier New;">public class Book {</span><br />
<span style="font-family: Courier New;"> <br />
@PrimaryKey</span><br />
<span style="font-family: Courier New;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span style="font-family: Courier New;"> private Key id;<br />
<br />
private String title;</span><br />
<br />
<span style="font-family: Courier New;"> @Persistent(mappedBy = "book")</span><br />
<span style="font-family: Courier New;"> @Element(dependent = "true")</span><br />
<span style="font-family: Courier New;"><span style="font-family: Courier New;"> @Order(extensions = @Extension(vendorName="<wbr></wbr>datanucleus", key="list-ordering", value="id asc"))<br />
</span> private List<Chapter> chapters = new ArrayList<Chapter>();</span><br />
<span style="font-family: Courier New;"><br />
// getters and setters<br />
}<br />
<br />
@PersistenceCapable(<wbr></wbr>identityType = IdentityType.APPLICATION, detachable = "true")</span><br />
<span style="font-family: Courier New;">public class Chapter {<br />
@PrimaryKey</span><br />
<span style="font-family: Courier New;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span style="font-family: Courier New;"> private Key id;</span><br />
<br />
<span style="font-family: Courier New;"> private String title;<br />
private int numPages;<br />
</span><br />
<span style="font-family: Courier New;"> @Persistent</span><br />
<span style="font-family: Courier New;"> private Book book;<br />
<br />
// getters and setters</span><br />
<span style="font-family: Courier New;">}</span><br />
<br />
Now let's assume we've already created a book with a few chapters in the datastore and we want to add a brand new chapter to a Book with a given id (we'll assume someone else is creating and closing a PersistenceManager named 'pm' for us):<br />
<br />
<br />
<span style="font-family: Courier New;">public void addChapterToBook(<wbr></wbr>PersistenceManager pm, Key bookKey, Chapter chapter) {<br />
pm.currentTransaction().begin(<wbr></wbr>);<br />
<span style="font-family: Courier New;"> try {<br />
// throws a runtime exception if book is not found <br />
Book b = pm.getObjectById(Book.class, bookKey);<br />
b.getChapters().add(chapter);</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> pm.currentTransaction().<wbr></wbr>commit();</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;">} finally {</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> if (pm.currentTransaction().<wbr></wbr>isActive()) {</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> pm.currentTransaction().<wbr></wbr>rollback();</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> }</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> }</span><br />
}<br />
------------------------------<wbr></wbr>--<br />
<br />
</span>The interesting thing about both of these examples is that we're not making any explicit calls to save the new Chapter. We look up the Book identified by the Key that was passed into the function and then we manipulate the persistent state of the object by manipulating the POJO that was returned by em.fetch/pm.getObjectById. JPA and JDO both have mechanisms that allow them to monitor the objects that you've looked up for changes. Ever wonder what exactly the enhancer is doing to your classes? It's adding hooks so that the persistence framework gets notified when things change (among other things). This allows JPA and JDO to automatically flush your changes to the datastore when you commit your transaction. If you wanted to modify the title of the Book or the number of pages in an existing Chapter the approach would be exactly the same: Start a transaction, look up the Book, make your changes, commit your transaction. Whether you're using JPA or JDO your changes will be persisted for you without any explicit calls to change the persistent state. This is a prime example of how JPA and JDO facilitate "transparent persistence."Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com13tag:blogger.com,1999:blog-4143308268271113602.post-61682944059153409962009-10-15T14:50:00.000-07:002009-10-15T14:50:24.740-07:00Executing Batch Gets(This was <a href="http://groups.google.com/group/google-appengine-java/browse_thread/thread/d907ba54c579e9ed/280eee96780bdd7b#280eee96780bdd7b">originally posted</a> to the Google App Java Google Group on September 21, 2009) <br />
<br />
Did you know <span class="il">that</span> the App Engine datastore supports batch gets? Batch gets are a super-efficient way to load multiple entities when you already have the keys of the entities you want loaded. Here's an example using the low-level Datastore API:<br />
<br />
<span style="font-family: Courier New;"> public Map<key, entity=""> getById(List<key> keys) {</key></key,></span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> DatastoreService ds = DatastoreServiceFactory.<wbr></wbr>getDatastoreService();</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> return ds.get(keys); </span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> }</span><br style="font-family: Courier New;" /><br />
Now lets see how we can accomplish the same thing in JPA and JDO:<br />
<br />
JPA:<br />
<span style="font-family: Courier New;">@Entity</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;">public class Book {</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> @Id</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> @GeneratedValue(strategy=<wbr></wbr>GenerationType.IDENTITY)</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> private Key key;</span><span style="font-family: Courier New;"><br />
private String title;</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"><br />
// additional members, getters and setters<br />
}</span><br />
<br />
<br />
<span style="font-family: Courier New;">public List<book> getById(List<key> keys) {</key></book></span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> Query q = em.createQuery("select from " + Book.class.getName() + " where key = :keys");</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> q.setParameter("keys", keys);</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> return (List<book>) q.getResultList();</book></span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;">}</span><br style="font-family: Courier New;" /><br style="font-family: Courier New;" /><br />
JDO:<br />
<span style="font-family: Courier New;">@PersistenceCapable(<wbr></wbr>identityType = IdentityType.APPLICATION)</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;">public class Book {</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> @PrimaryKey</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span style="font-family: Courier New;"> @GeneratedValue(strategy=<wbr></wbr>GenerationType.IDENTITY)</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> private Key key;</span><span style="font-family: Courier New;"><br />
private String title;</span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"><br />
// additional members, getters and setters<br />
}</span><br />
<br />
<span style="font-family: Courier New;">public List<book> getById(List<key> keys) {</key></book></span><br style="font-family: Courier New;" /><span style="font-family: Courier New;"> Query q = pm.newQuery("select from " + Book.class.getName() + " where key == :keys");</span><br style="font-family: Courier New;" /> <span style="font-family: Courier New;"> return (List<book>) q.execute(keys);</book></span><br style="font-family: Courier New;" /><span style="font-family: Courier New;">}</span><br style="font-family: Courier New;" /><br style="font-family: Courier New;" /> Notice how in both examples we're constructing a query to pull back the entities by key. The App Engine JDO/JPA implementation detects queries <span class="il">that</span> are filtering only by key and fulfills them using a low level batch get rather than a datstore query. This works no matter the type of your primary key field, so whether you're using a Long, an unencoded String, an encoded String, or a Key, the same technique will <span class="il">work</span>. However, even though this looks like a query, all the fetch-related transaction restrictions apply: if you're executing your batch get query inside a txn, all of the entities you're attempting to fetch must belong to the same entity group or you'll get an exception. Be careful with this. <br />
<br />
The next time you need to pull back multiple entities and you already have their keys, issue a query <span class="il">that</span> filters only by your object's key field and reap the benefits of the datastore's optimized batch get mechanism.Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com20tag:blogger.com,1999:blog-4143308268271113602.post-81087582775165774552009-10-15T14:37:00.000-07:002010-03-04T19:39:24.499-08:00Creating A Bidrectional Owned One-To-Many(This was <a href="http://groups.google.com/group/google-appengine-java/browse_thread/thread/54c83dc6242fd633/1c62a01bc2a5b6dc#1c62a01bc2a5b6dc">originally posted</a> to the Google App Java Google Group on September 14, 2009) <br />
<br />
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.<br />
<br />
JPA:<br />
<span style="font-family: 'Courier New';">import com.google.appengine.api.datastore.Key;</span><br />
<span style="font-family: 'Courier New';">//...</span><br />
<span style="font-family: 'Courier New';"></span><br />
<span style="font-family: 'Courier New';">@Entity</span><br />
<span style="font-family: 'Courier New';">public class Book {</span><br />
<span style="font-family: 'Courier New';"> @Id</span><br />
<span style="font-family: 'Courier New';"> @GeneratedValue(strategy=<wbr></wbr>GenerationType.IDENTITY)</span><br />
<span style="font-family: 'Courier New';"> private Key id;<br />
</span><span style="font-family: 'Courier New';"><br />
private String title;</span><br />
<span style="font-family: 'Courier New';"><br />
@OneToMany(mappedBy = "book", cascade = CascadeType.ALL)</span><br />
<span style="font-family: 'Courier New';"> private List<Chapter> chapters = new ArrayList<Chapter>();</span><br />
<span style="font-family: 'Courier New';"><br />
// getters and setters<br />
}<br />
<br />
</span><br />
<span style="font-family: 'Courier New';">@Entity</span><br />
<span style="font-family: 'Courier New';">public class Chapter {</span><br />
<span style="font-family: 'Courier New';"> @Id</span><br />
<span style="font-family: 'Courier New';"> @GeneratedValue(strategy=<wbr></wbr>GenerationType.IDENTITY)</span><br />
<span style="font-family: 'Courier New';"> private Key id;</span><br />
<br />
<span style="font-family: 'Courier New';"> private String title;<br />
private int numPages;<br style="font-family: Courier New;" /> </span><br />
<span style="font-family: 'Courier New';"> @ManyToOne(fetch = FetchType.LAZY)</span><br />
<span style="font-family: 'Courier New';"> private Book book;<br />
<br />
</span><span style="font-family: 'Courier New';"> // getters and setters</span><br />
<span style="font-family: 'Courier New';">}</span><br />
<br />
Now let's create a book with two chapters (we'll assume someone else is creating and closing an EntityManager named 'em' for us):<br />
<br />
<span style="font-family: 'Courier New';">Book b = new Book();</span><br />
<span style="font-family: 'Courier New';">b.setTitle("JPA 4eva");</span><br />
<span style="font-family: 'Courier New';">Chapter c1 = new Chapter();</span><br />
<span style="font-family: 'Courier New';">c1.setTitle("Intro");</span><br />
<span style="font-family: 'Courier New';">c1.setNumPages(10);</span><br />
<span style="font-family: 'Courier New';">b.getChapters().add(c1);</span><br />
<span style="font-family: 'Courier New';">Chapter c2 = new Chapter();</span><br />
<span style="font-family: 'Courier New';">c2.setTitle("Configuration");</span><br />
<span style="font-family: 'Courier New';">c2.setNumPages(9);</span><br />
<span style="font-family: 'Courier New';">b.getChapters().add(c2);</span><br />
<br />
<span style="font-family: 'Courier New';">em.getTransaction().begin();</span><br />
<span style="font-family: 'Courier New';">try {</span><br />
<span style="font-family: 'Courier New';"> em.persist(b);</span><br />
<span style="font-family: 'Courier New';"> em.getTransaction().commit();</span><br />
<span style="font-family: 'Courier New';">} finally {</span><br />
<span style="font-family: 'Courier New';"> if (em.getTransaction().isActive(<wbr></wbr>)) {</span><br />
<span style="font-family: 'Courier New';"> em.getTransaction().rollback()<wbr></wbr>;</span><br />
<span style="font-family: 'Courier New';"> }</span><br />
<span style="font-family: 'Courier New';">}</span><br />
<br />
<br />
JDO:<br />
<br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New';">import com.google.appengine.api.datastore.Key;</span><br />
</div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="font-family: 'Courier New';">//...</span><br />
</div><div><span style="font-family: 'Courier New';"></span><br />
</div><span style="font-family: 'Courier New';">@PersistenceCapable(<wbr></wbr>identityType = IdentityType.APPLICATION, detachable = "true")</span><br />
<span style="font-family: 'Courier New';">public class Book {</span><br />
<span style="font-family: 'Courier New';"> <br />
@PrimaryKey</span><br />
<span style="font-family: 'Courier New';"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span style="font-family: 'Courier New';"> private Key id;<br />
<br />
private String title;</span><br />
<br />
<span style="font-family: 'Courier New';"> @Persistent(mappedBy = "book")</span><br />
<span style="font-family: 'Courier New';"> @Element(dependent = "true")</span><br />
<span style="font-family: 'Courier New';"> private List<Chapter> chapters = new ArrayList<Chapter>();</span><br />
<span style="font-family: 'Courier New';"><br />
// getters and setters<br />
}<br />
<br />
@PersistenceCapable(<wbr></wbr>identityType = IdentityType.APPLICATION, detachable = "true")</span><br />
<span style="font-family: 'Courier New';">public class Chapter {<br />
@PrimaryKey</span><br />
<span style="font-family: 'Courier New';"> @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)</span><br />
<span style="font-family: 'Courier New';"> private Key id;</span><br />
<br />
<span style="font-family: 'Courier New';"> private String title;<br />
private int numPages;<br />
</span><br />
<span style="font-family: 'Courier New';"> @Persistent</span><br />
<span style="font-family: 'Courier New';"> private Book book;<br />
<br />
// getters and setters</span><br />
<span style="font-family: 'Courier New';">}</span><br />
<br />
Now let's create a book with two chapters (we'll assume someone else is creating and closing a PersistenceManager named 'pm' for us):<br />
<br />
<span style="font-family: 'Courier New';">Book b = new Book();</span><br />
<span style="font-family: 'Courier New';">b.setTitle("JDO 4eva");</span><br />
<span style="font-family: 'Courier New';">Chapter c1 = new Chapter();</span><br />
<span style="font-family: 'Courier New';">c1.setTitle("Intro");</span><br />
<span style="font-family: 'Courier New';">c1.setNumPages(10);</span><br />
<span style="font-family: 'Courier New';">b.getChapters().add(c1);</span><br />
<span style="font-family: 'Courier New';">Chapter c2 = new Chapter();</span><br />
<span style="font-family: 'Courier New';">c2.setTitle("Configuration");</span><br />
<span style="font-family: 'Courier New';">c2.setNumPages(9);</span><br />
<span style="font-family: 'Courier New';">b.getChapters().add(c2);</span><br />
<br />
<span style="font-family: 'Courier New';">pm.currentTransaction().begin(<wbr></wbr>);</span><br />
<span style="font-family: 'Courier New';">try {</span><br />
<span style="font-family: 'Courier New';"> pm.makePersistent(b);</span><br />
<span style="font-family: 'Courier New';"> pm.currentTransaction().<wbr></wbr>commit();</span><br />
<span style="font-family: 'Courier New';">} finally {</span><br />
<span style="font-family: 'Courier New';"> if (pm.currentTransaction().<wbr></wbr>isActive()) {</span><br />
<span style="font-family: 'Courier New';"> pm.currentTransaction().<wbr></wbr>rollback();</span><br />
<span style="font-family: 'Courier New';"> }</span><br />
<span style="font-family: 'Courier New';">}</span>Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com38tag:blogger.com,1999:blog-4143308268271113602.post-8033552572817156342009-10-15T14:32:00.000-07:002009-10-15T14:32:38.233-07:00Welcome to the GAE/J Persistence Blog!Greetings everyone! My name is Max Ross and I'm an engineer on the Google App Engine team who works on persistence stuff: the datastore, the low-level datastore api, and the JDO/JPA implementation. I'll be your host.<br />
<br />
GAE for Java has been available since early April 2009, and since launch we've <a href="http://code.google.com/p/datanucleus-appengine/issues/list?can=1&q=status%3AFixed%2CVerified" target="_blank">fixed a lot of bugs and added a lot of features</a> related to JDO and JPA. However, I read the <a href="http://groups.google.com/group/google-appengine-java">GAE Java group</a> regularly and I see the persistence questions being asked, and my impression is <span class="il">that</span> we should be giving you, our customers, more guidance. This blog<span class="il"></span><span class="il"></span><span class="il"></span> is my attempt to provide <span class="il">that</span> additional guidance.<br />
<br />
Each week I'm going to post a complete example demonstrating how to accomplish a specific persistence-related task. We don't play favorites here on the App Engine team so I'll construct the example in both JDO and JPA, and I'll try to cover a wide range of topics to keep everyone interested. My hope is <span class="il">that</span>, over time, this will come to serve as a valuable resource to all App Engine Java developers whether you're new to JDO/JPA or not.<br />
<br />
Thanks for reading!Maxhttp://www.blogger.com/profile/10795965078834917984noreply@blogger.com11