Reputation: 35792
I can't find any documentation on the appropriate way to make a many-to-many relationship between objects using Objectify on Google App Engine.
Can anyone explain how to do this? Do I need to create a new "join" class for this? How efficient will it be?
Upvotes: 5
Views: 2111
Reputation: 21
I've solved using this approach with Objectify 4.0 :
@Entity
@Index
public class Module {
@Id
private Long id;
private String name;
@Load
private List<Ref<Template>> templates;
public List<Template> getTemplates() {
List<Template> templates = new ArrayList<Template>();
for (Ref<Template> temp : this.templates) {
templates.add(temp.get());
}
return templates;
}
public void setTemplates(List<Template> templatesParm) {
List<Ref<Template>> templates = new ArrayList<Ref<Template>>();
for (Template temp : templatesParm) {
templates.add(Ref.create(temp));
}
this.templates = templates;
}
Upvotes: 2
Reputation: 1919
Let's think about one-many for a moment; if you want an object A to "have many" object B-s, there are only two ways to do it:
The Relational Way: make each B point at the A. When you have the A0 and want all the B-s that relate to it, just query for the B-s which point to the given A0.
The NoSQL / ObjectStore way: make A have a field that holds a list of pointers (Keys) to the B-s. Note that this way also allows the B-s to be in a particular order (despite the GAE/Java docs to the contrary.)
Which one is best depends. The ObjectStore way is limited by the size of an object. The Relational way is subject to a subtle problem in that unless A and all the B-s are in the same Entity Group and you do an Ancestor Query in a Transaction (or maybe even if it isn't in a Transaction) you are guaranteed to get all of the B-s which point to that A. However if the A and B-s span Entity Groups, it is possible (though perhaps unlikely) that you will get a B that does not satisfy the query predicate, or miss a B that does: https://developers.google.com/appengine/articles/transaction_isolation
In the (now standard) High Replication Datastore, the transaction typically is completely applied within a few hundred milliseconds after the commit returns. However, even if it is not completely applied, subsequent reads, writes, and ancestor queries will always reflect the results of the commit, because these operations apply any outstanding modifications before executing. However, queries that span multiple entity groups cannot determine whether there are any outstanding modifications before executing and may return stale or partially applied results.
Now for many-many: I read a story once that described going to the toilet in space; there were four combinations: inside/outside a space ship and the two kinds of going to the toilet. For the last combination of being outside the ship (in a space suit) and eliminating solids, the only answer was "there ain't no graceful way" (also the title of the article): http://settlement.arc.nasa.gov/CoEvolutionBook/SPACE.HTML#There Ain't No Graceful Way ... and that is also the answer to many-many relationships in GAE. You can build them using a join class and each side of the join could be implemented with a query or a list of Keys.
Upvotes: 1
Reputation: 1217
This is not the best approach to map many to many relationships in Objectify. The best way is to create an entity that maps the relationship. For example, suppose you have two objects A and B, and they are associated in a certain way. They you could create a class similar to:
Class Link{
Key<?> master;
key<?> slave;
public Link(){
}
public setLink(Entity master, Entity slave){
//initialize
}
}
Then you may create a Link entity to model a relationship. This automatically maps one to one or many to many relationships
Upvotes: 2
Reputation: 20890
What kinds of queries do you need to support?
The simplest solution is:
@Entity
public class StoredObject {
@Id
private Long id;
private List<Long> relatedIds;
}
then, given a StoredObject
, you can call objectify.get(StoredObject.class, storedObject.getRelatedIds())
to fetch all the related ids.
To speed up some queries in my own app I did create a few join classes. The expense comes at write-time (you have to maintain the joins) but then read-time is a single index scan with consecutive results!
Upvotes: 2