![]() |
![]() |
Back to the Speedo documentation
Speedo supports detach/attach operations. Once made
persistent, an
object can be detached from the cache : the user gets a copy of the
persistent instance. The copy can be modified either
in the same JVM or in a different JVM. Then, the copy can be
re-attached: this operation commits the changes on the cache.
To enable such operations on objects, the class must be defined as
detachable in the jdo file as follows:
<class name="Player" identity-type="application" detachable="true">
The following code sample shows the way to perform a detach
operation. This code is based on a Team class containing a collection
of Players and a Coach.
//firstly, define a team with 2 players and a coach
Team team = new Team("Bordeaux",null,null);
Collection players = new ArrayList();
Player player1 = new Player("p1", t, 25);
players.add(player1);
Player player2 = new Player("p2", t, 32);
players.add(player2);
team.setPlayers(players);
Coach coach = new Coach("c1", 5, team);
team.setCoach(coach);
//then, get the persistence manager and make the team persistent
PersistenceManager pm = pmf.getPersistenceManager();
pm.currentTransaction().begin();
pm.makePersistent(team);
pm.currentTransaction().commit();
//finally, detach the team
Team copyOfTeam = (Team) pm.detachCopy((Object)team);
pm.close();
The detach operation can be performed indifferently within or
outside a transaction. When detaching an object, all references to
other persistent objects are replaced by the corresponding detached
copies.
Detaching an object not yet persistent makes it persistent before
returning a copy of the object. When detaching a persistent-new or a
persistent-dirty object, updates of this object are flushed prior to
detachment: if the transaction in which the flush is performed rolls
back, then the detached copy becomes invalid (no re-attachment is
allowed).
The attach operation has to be performed within an active transaction. The attach is performed by a call to the makePersistent method. When performing an attach, changes made to a copy while detached are applied to the corresponding persistent instance in the cache. If the object being attached is not yet persistent, it is firstly made persistent.
The following code sample shows the way to perform an attach
operation.
This code is also based on a Team class containing a collection of
Players
and a Coach.
//firstly, define a team with 2 players and a coach
Team team = new Team("Bordeaux",null,null);
Collection players = new ArrayList();
Player player1 = new Player("p1", team, 25);
players.add(player1);
Player player2 = new Player("p2", team, 32);
players.add(player2);
team.setPlayers(players);
Coach coach = new Coach("c1", 5, team);
team.setCoach(coach);
//then, get the persistence manager and make the team persistent
PersistenceManager pm = pmf.getPersistenceManager();
pm.currentTransaction().begin();
pm.makePersistent(team);
pm.currentTransaction().commit();
//detach the team and modify the detached copy
Team copyOfTeam = (Team) pm.detachCopy((Object)team);
copyOfTeam.getCoach().setExperience(99);
//finally attach the team
pm.currentTransaction().begin();
Team attachedTeam = (Team) pm.makePersistent(copyOfTeam);
pm.currentTransaction().commit();
pm.close();
The object returned by the attach method is the persistent object
corresponding to the detached instance.
A fetch group defines a particular loaded state for an object
graph.
It specifies fields to be loaded for all the instances in the graph.
Speedo uses fetch groups for detach, refresh and retrieve operations.
public class Person{
String name;
int age;
Address address;
Set children;
}
public class Address{
String street;
String city;
Country country;
}
public class Country{
String code;
String name;
}
<class name="Person" ...>
...
<fetch-group name="myFirstFG">
<field name="name"/>
<field name="age"/>
<field name="address"/>
</fetch-group>
...
</class>
<class name="Person" ...>Activating this fetch group, the address field will be first loaded with only the country field. And then, the country field will be loaded with only the code field.
...
<fetch-group name="referenceFG">
<field name="address.country.code"/>
</fetch-group>
...
</class>
<class name="Person" ...>The name and age fields will be loaded because of the nested fetch group "values" and the children field will also be loaded.
...
<fetch-group name="nestedFG">
<fetch-group name="values"/>
<field name="children"/>
</fetch-group>
...
</class>
<class name="Person" ...>The name, age, address will be loaded and it will be the same for the children of the person being detached, refreshed or retrieved. The children of the children will not be loaded.
...
<fetch-group name="depthFG">
<fetch-group name="default"/>
<field name name="address"/>
<field name="children" depth="1"/>
</fetch-group>
...
</class>
<class name="Person" ...>Let's consider that the active fetch group is the "detail" fetch group. The fields name, age and address will be loaded for the person being detached, refreshed or retrrieved. For the field children, the fetch group "list" will be used: the name and age of each child will be loaded.
...
<fetch-group name="list">
<field name="name"/>
<field name name="age"/>
</fetch-group>
<fetch-group name="detail">
<fetch-group name="default"/>
<field name="address"/>
<field name="children" fetch-group="list"/>
</fetch-group>
...
</class>
public class Node{
String name;
Map edges; //name -> EdgeWeight
}
public class EdgeWeight{
int weight;
}
<class name="Node" ...>If the "keyValue" fetch group is active, both keys and values of elements in the map of the node object will be loaded.
...
<fetch-group name="keyValue">
<field name="edges#key"/>
<field name="edges#value"/>
</fetch-group>
<fetch-group name="keyOnly">
<field name="edges#key"/>
</fetch-group>
...
</class>
PersistenceManager pm = pmf.getPersistenceManager();The application is then ready to detach, refresh and retrieve instances using the fetch groups.
FetchPlan fp = pm.getFetchPlan();
fp.addGroup("detail").removeGroup("default"); //only the detail fg is active
Speedo supports the JDO 1 & 2 queries specification. To understand the basics of the JDO queries, it is adviced to read the step 4 of chapter Additional 1 in Speedo Tutorial. The following chapter describes JDO 2 features not yet integrated into the tutorial. Here is the plan of this chapter.
The application might want to get results from a query that are not instances of the candidate class. The results might be fields of persistent instances, instances of classes other than the candidate class, or aggregates of fields.
void setResult(String result);
The result parameter consists of the optional keyword distinct followed by a comma separated list of named result expressions.The result expressions include:
The application may have a user-defined class that best represents the results of a query. In this case, the application can specify that instances of this class should be returned.
void setResultClass(Class resultClass);
The default result class is the candidate class if the parameter to setResult is null or not specified. When the result is specified and not null, the default result class is the type of the expression if the result consists of one expression, or Object[] if the result consists of more than one expression.
Select distinct department names of all employees
Query query = pm.newQuery(Employee.class);
query.setResult("distinct dept.name");
List l = (List) query.execute();
for(Iterator it = l.iterator(); it.hasNext();) {
String deptName = (String) it.next();
}
query.closeAll();
Select distinct department of employees which have a salary greater than 2000
Query query = pm.newQuery(Employee.class);
query.setFilter("salary > 2000");
query.setResult("distinct dept");
List l = (List) query.execute();
for(Iterator it = l.iterator(); it.hasNext();) {
Department dept = (Department) it.next(); } query.closeAll();
Compute the average of employee salary
Query query = pm.newQuery(Employee.class);
query.setResult("AVG(salary)");
List l = (List) query.execute();
Float avg = (Float) l.get(0); query.closeAll();
Compute the average of employee salary
Query query = pm.newQuery(Employee.class);
query.setUnique(true);
query.setResult("AVG(salary)");
Float avg = (Float) query.execute();
query.closeAll();
Select distinct department names of all employees
Query query = pm.newQuery(Employee.class);
query.setResult("distinct dept.name");
List l = (List) query.execute();
for(Iterator it = l.iterator(); it.hasNext();) {
String deptName = (String) it.next();
}
query.closeAll();
Select distinct (department name, a salary of an employee) into a user class
class StringFloat {
public String name;
public Float value;
public StringFloat() {}
public StringFloat(String n, Float v) {
name = n;
value = v;
}
public String setName(String n) {name = n;}
public String setValue(Float f) {value = f;}
}
...
Query query = pm.newQuery(Employee.class);
query.setResult("distinct dept.name, salary");
query.setResultClass(StringFloat.class);
List l = (List) query.execute();
for(Iterator it = l.iterator(); it.hasNext();) {
StringFloat sf = (StringFloat) it.next();
}
query.closeAll();
Select distinct (department name, a salary of an employee) into a user class
Query query = pm.newQuery(Employee.class);
query.setResult("distinct dept.name, salary");
List l = (List) query.execute();
for(Iterator it = l.iterator(); it.hasNext();) {
Object[] os = (Object[]) it.next();
String deptName = (String) os[0];
Float empSalary = (Float) os[1];
}
query.closeAll();
Aggregates are most useful if they can be grouped based on an element of the result. Grouping is required if there are non-aggregate expressions in the result.
void setGrouping(String grouping);
The grouping parameter consists of one or more expressions separated by commas followed by an optional "having" followed by one Boolean expression. When grouping is specified, each result expression must be one of:
The query groups all elements where all expressions specified in
setGrouping have the
same values. The query result consists of one element per group.
When "having" is specified, the "having" expression consists of
arithmetic and boolean
expressions containing aggregate expressions.
IMPORTANT: The having clause is not yet supported.
class StringFloat {
public String name;
public Float value;
public StringFloat() {}
public StringFloat(String n, Float v) {
name = n;
value = v;
}
public String setName(String n) {name = n;}
public String setValue(Float f) {value = f;}
}
...
Query query = pm.newQuery(Employee.class);
query.setResult("dept.name, avg(salary)");
query.setGrouping("dept.name");
query.setResultClass(StringFloat.class);
List l = (List) query.execute();
for(Iterator it = l.iterator(); it.hasNext();) {
StringFloat sf = (StringFloat) it.next();
}
query.closeAll();
The application may want to skip some number of results that may have been previously returned, and additionally maywant to limit the number of instances returned from a query. The parameters are modeled after String.getChars and are 0-origin. The parameters are not saved if the query is serialized. The default range for query execution if this method is not called are (0, Integer.MAX_VALUE).
setRange(int fromIncl, int toExcl);
The fromIncl parameter is the number of instances of the query
result to skip over before
returning the Collection to the user. If specified as 0 (the default),
no instances are
skipped.
The toExcl parameter is the last instance of the query result (before
skipping) to return
to the user.
The expression (toExcl - fromIncl) is the maximum number of instances
in the query
result to be returned to the user. If fewer instances are available,
then fewer instances
will be returned.
Query query = pm.newQuery(Employee.class);
query.setRange(50,10O);
List l = (List) query.execute();
assertTrue(50 >= l.size());
Speedo supports the following types, which means that a persistent field can be declared with one of these types:
Speedo supports delete cascade on objects relations. When a
persistent object is deleted, you can tell Speedo to delete some of the
objects referenced by the deleted object.
To enable such operations on objects, you must use the "cascade-delete"
extension in the definition
of the relation between two objects. This is done in the jdo file as
follows:
<class name="A">
<field name="theRefToB" persistence-modifier="persistent">
<extension vendor-name="speedo" key="target-foreign-keys" value="PKB=FKB"/>
<extension vendor-name="speedo" key="cascade-delete" value="true"/>
</field>
<field name="collectionOfCs" persistence-modifier="persistent">
<collection element-type="C"/>
<extension vendor-name="speedo" key="target-foreign-keys" value="PKC=FKC"/>
<extension vendor-name="speedo" key="reverse-field" value="thefC"/>
<extension vendor-name="speedo" key="cascade-delete" value="true"/>
</field>
</class>
...
<class name="B">...</class>
...
<class name="C">...</class>
In this case, when the ProxyManager.deletePersistent(Object o) is called on an A instance, here is what happens :