This syntax is only available for GAE implementation for the time being but will be ported soon to other impl
The following code samples can be found in samples dir
Many<T>
represents the relation itself from the owner/aggregator entity to the owned or aggregated children entities in a One2Many/Many2One relation.Many<T>
is set on the owner side in a multiple owned relation.Many<T>
is set on the aggregator side in a multiple aggregated relation.Many<T>
provides a lazy way to retrieve the children of the relation/aggregation as a List or as a Query.
Note with Many<T>
, you don’t have to choose in your model how you want to manipulate your child entities: you will be able to do it through a simple list retrieved at once or through a query with limits, offsets, filters, ordering etc…
Keep in mind that Siena just manages the relation but not the objects themselves
- manage the relation but not the full life-cycle of related entities (you must manage their updates for ex).
- manage relation change when you add a child to the One2Many relation.
- auto-insert the related entities at the same time as the parent (or when updating the parent) if not inserted yet.
- lazy-load the related entities only when required.
- delete-cascade the aggregated entities because an aggregated entity can’t exist outside its aggregator. This is the ONLY accepted delete cascade.
- manage the updates of data within the related entities. You must save them yourself.
- delete the owned entities when you delete the owner since an owned entity can change owner. Nevertheless, the owned entity is no more associated to any owner if you delete the owner.
public class OwnerModel {
...
@Owned(mappedBy="owner")
// @Owned
// is also valid: the mappingBy is not mandatory and if not precised, siena will find the first field having OwnerModel type
public Many<ChildModel> child;
...
}
Here is the child model:
public class ChildModel {
...
public OwnerModel owner;
...
}
Note On the child side, the owner field is just written as a simple reference to the owner without any One<T>
. Why? because, on the owned entity side, the owner is just referenced by its id/key.
Note On the owner side, the @Owned.mappedBy
is used to specify which field in the child maps the owner. But the mappedBy
is not mandatory and Siena can deduce the field by itself.
Using the old notation Query<T>
, the Many<T>
is equivalent to:
public class OwnerModel {
...
@Filter("owner")
public Query<ChildModel> child;
...
}
Here is the aggregator model:
public class AggregatorModel {
...
@Aggregated
public Many<ChildModel> child;
...
}
Here is the aggregated model:
public class AggregatedModel {
...
...
}
Note On the aggregated side, there is no reference to the aggregator because an aggregated entity shall not be aware of its aggregator: it’s a part of it but it was not necessarily meant to be.
It’s very useful because you don’t need to foresee your model will be aggregated while designing it and you can aggregate any model.
The aggregator reference is managed by Siena itself and you don’t have to deal with it neither declare it in your model.
Note Nevertheless, if you absolutely want to access your own aggregator, it’s possible. Go to the aggregated relation page.
The 2 important functions of Many<T>
are:
SyncList<T> asList()
: retrieves the elements as a SyncList which is a classic List with some (re)synchronization featuresQuery<T> asQuery()
: retrieves the elements as a siena Query allowing to apply filters/orders/etc…
The 2 important functions of SyncList<T>
are:
- @SyncList sync()* : re-synchronizes only if the list was not already fetched from datastore (if the elements have changed since, it can’t know it so it won’t synchronize)
SyncList<E> forceSync()
: forces full re-synchronization of the list with the datastoreboolean addAll(F ...c)
: a facility function missing in traditional Java Listboolean addAll(int index, F ...c)
: a facility function missing in traditional Java List
// creates dog
Dog pluto = new Dog("Pluto");
// infects dog with fleas
for(int i=0; i<10; i++){
Flea flea = new Flea("pupuce"+i);
pluto.fleas.asList().add(flea);
}
//saves dog + fleas in one single call
pluto.save();
Note You don’t have to insert the children objects as Siena inserts them at the same time as the parent (if the children were not inserted yet).
Note using asQuery()
has no effect when inserting because it would try to query the datastore. Only asList()
when creating the parent + children.
// gets the dog
Dog pluto = Dog.getByName("Pluto");
// here the fleas are not yet fetched from datastore
// ... later ...
// gets the little bloodsuckers
List<Flea> fleas = pluto.fleas.asList();
Note pluto.fleas is a Many<Flea>
so you need to use asList()
to fetch all the children
You can first insert a parent
Then, you create children along time and associate those children to the parent and insert them.
Finally, you can retrieve all children at once from the parent using asList()
// inserts dog Dog pluto = new Dog("Pluto"); pluto.insert();
// infects dog with fleas for(int i=0; i<10; i++){ Flea flea = new Flea("pupuce"+i); // associates the flea to its dog flea.bloodsource = pluto; flea.insert(); }
// retrieves all the fleas from the dog // gets the dog Dog plutobis = Dog.getByName("Pluto"); // here the fleas are not yet fetched from datastore // ... later ... // gets the little bloodsuckers List<Flea> fleas = plutobis.fleas.asList();
Note The children associate themselves to the parent but Many<T>
retrieves them all from the parent afterwards.
// removes a few flea (5 & 8) for the poor dog plutobis.fleas.asList().remove(5); plutobis.fleas.asList().remove(7); plutobis.fleas.asList().add(new Flea("pupuce10")); // updates the dog plutobis.update();
// retrieve again the dog and verifies the fleas 5/8 have disappeared Dog plutoter = Dog.getByName("Pluto"); assertEquals(9, plutoter.fleas.asList().size());
// retrieves the removed fleas and verifies there are not associated to the dog anymore Flea flea5 = Flea.getByName("pupuce5"); assertNull(flea5.bloodsource); Flea flea8 = Flea.getByName("pupuce8"); assertNull(flea8.bloodsource); Flea flea10 = Flea.getByName("pupuce10"); assertEquals(pluto.id, flea8.bloodsource.id);
Note that the children are removed from the list but they are also de-associated from the parent BUT they are NOT deleted.
// gets a flea from the dog Flea flea = pluto.fleas.asList().get(5);
flea.name = "pupuce5_UPD";
// if you update the dog, the flea is not updated pluto.update();
// retrieves the same flea Flea fleabis = pluto.fleas.asList().get(5); assertFalse(flea.name.equals(fleabis.name));
// now updates the flea manually flea.update();
// retrieves again the dog pluto = Dog.getByName("Pluto"); // retrieves the same flea fleabis = pluto.fleas.asList().get(5); // verifies it was updated now assertEquals(fleabis.name, flea.name);
Note Siena JUST MANAGES THE RELATION and do not update your objects if you modify them, it’s your responsability
// creates dog Dog pluto = new Dog("Pluto"); // creates fleas List<Flea> fleas = new ArrayList<Flea>(); for(int i=0; i<10; i++){ Flea flea = new Flea("pupuce"+i); fleas.add(flea); } //infects dog with fleas //Just to show addAll(Object...) pluto.fleas.asList().addAll(fleas); //saves dog + fleas pluto.save();
// fetches the list a first time fleas = pluto.fleas.asList();
// gets flea n°5 independently of the dog and updates it Flea flea = Model.getByKey(Flea.class, fleas.get(5).id); flea.name = flea.name + "_UPD"; flea.update();
// synchronizes dog's fleas list (it doesn't re-fetch the list if already done) pluto.fleas.asList().sync(); // verifies the updated flea is not synchronized Flea fleabis = pluto.fleas.asList().get(5); // verifies it was updated assertFalse(fleabis.name.equals(flea.name));
// forces synchronizing the list (it re-fetches the fleas) pluto.fleas.asList().forceSync(); // retrieves the same flea fleabis = pluto.fleas.asList().get(5); // verifies it was updated assertEquals(fleabis.name, flea.name);
Note sync()
doesn’t re-fetch the list as it was already fetched.
Note forceSync()
forces the re-fetch of the list and all elements are synchronized with the datastore. Like that, you control exactly how you want to fetch data from the datastore after the first synchronization.
asQuery()
is just an entrypoint to the Query world. Instead of getting all elements as asList()
does, you can fetch only the elements you want by using query filtering/ordering/searching.
Here are a few samples:
// creates dog Dog pluto = new Dog("Pluto"); // infects dog with fleas having several times the same name Flea alpha1 = new Flea("alpha"); Flea alpha2 = new Flea("alpha"); Flea beta1 = new Flea("beta"); Flea beta2 = new Flea("beta");
//saves dog + fleas with addAll pluto.fleas.asList().addAll(alpha1, alpha2, beta1, beta2); pluto.save();
// retrieves again the dog pluto = Dog.getByName("Pluto");
// fetches alpha fleas List<Flea> alphas = pluto.fleas.asQuery().filter("name", "alpha").order("id").fetch(); assertEquals(alpha1.name, alphas.get(0).name); assertEquals(alpha1.id, alphas.get(0).id); assertEquals(alpha2.name, alphas.get(1).name); assertEquals(alpha2.id, alphas.get(1).id);
// interates on beta fleas Iterable<Flea> betas = pluto.fleas.asQuery().filter("name", "beta").order("id").iter(); int i=0; for(Flea beta:betas){ if(i==0) { assertEquals(beta1.name, beta.name); assertEquals(beta1.id, beta.id); } else if(i==1) { assertEquals(beta2.name, beta.name); assertEquals(beta2.id, beta.id); } i++; }
Note addAll(Collection)
is a convenient function to add several elements to a list without putting them in a Collection.
Note once you have a query with asQuery()
, you can do what you want with it as it is a query ;)