Speedo user manual: Speedo features

 FinalLogo.jpg
 FinalLogo.jpg
  1. Detach/Attach
  2. Fetch Groups
  3. JDO Queries
  4. Supported types
  5. Cascade delete

Back to the Speedo documentation




This chapter describes some particular features provided by Speedo. Of course Speedo supports the JDO API. However some features described here correspond to JDO 2. As the JDO 2.0 specification is currently not final, the API could change. However it seems that the specification should not change lot.

  1. Detach/Attach
  2. 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">

    Detaching instances

    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).

    Attaching instances

    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.

  3. Fetch Groups
  4. Definition

    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.

    Predefined fetch groups

      Within Speedo, four fetch groups are predefined:
    1. default:
    2.             The default group is based on the "default-fetch-group" attribute value of the field definition. It is composed of all the fields defined as belonging to the default fetch group in the jdo file.
                   The default group can be redefined as a normal fetch group by the user in the jdo files.
    3. values:
    4.             The values group is composed of all fields that are included in the default group by default (primitive fields, String, etc...).
                   As the default group, the values group can also be redefined by the user in the jdo files.
    5. none:
    6.             The none group contains only primary key fields.
    7. all:
    8.             The all group contains all fields in the class.

    Defining fetch groups

       The user can define his own fetch groups within the jdo files associated to the used objects by the application.
       The examples are based on the following classes:

    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;
    }

               With the following fetch group, the name, age and address of all the persons being detached (resp. refreshed, retrieved) will be detached (resp. refreshed, retrieved). The children field                 will not be loaded:
       
    <class name="Person" ...>
    ...
    <fetch-group name="myFirstFG">
    <field name="name"/>
    <field name="age"/>
    <field name="address"/>
    </fetch-group>
    ...
    </class>
                A dot-separated expression identifies a "to be loaded" field reachable from the class by navigating a reference:

    <class name="Person" ...>
    ...
    <fetch-group name="referenceFG">
    <field name="address.country.code"/>
    </fetch-group>
    ...
    </class>
                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.
                Note: Via the use of JORM, Speedo loads all the primitive fields of a class even if they are not defined in the active fetch group. This means that in the previous example, fields street             and city of the address will be loaded as the code field for the country.
                A fetch group definition can contain nested fetch group(s): the named nested fetch group(s) are to be included to define the graph to load. Nested fetch group elements are limited to                 olny the name attribute. It is not permitted to nest entire fetch group definitions.

    <class name="Person" ...>
    ...
    <fetch-group name="nestedFG">
    <fetch-group name="values"/>
    <field name="children"/>
    </fetch-group>
    ...
    </class>
                The name and age fields will be loaded because of the nested fetch group "values" and the children field will also be loaded.
                Recursive fetch group references are controlled by the depth attribute. If no depth is defined, the whole graph of instances reachable from this field will be fetched. It is the case for the             previous example: the children of the person detached, refreshed or retrieved will be loaded, as the children of these children, and etc...
       
    <class name="Person" ...>
    ...
    <fetch-group name="depthFG">
    <fetch-group name="default"/>
    <field name name="address"/>
    <field name="children" depth="1"/>
    </fetch-group>
    ...
    </class>
               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.

                If one or more depths have been specified then the largest depth is used unless one of the depths has not beeen specified (unlimited overrides other depth specifications).
                For a field within a fetch group, an additional fetch group may be specified: this fetch group is then considered to be active just for the loading of the field.
       
    <class name="Person" ...>
    ...
    <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>
                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.

        The example below is based on the following classes:

    public class Node{
    String name;
    Map edges; //name -> EdgeWeight
    }

    public class EdgeWeight{
    int weight;
    }
                For maps, "#key" or "#value" may be appended to the name of the map field to navigate the key or value respectively (e.g. to include a field of the key  class or value class in the fetch             group). If nothing is appended, both the key and the value will be loaded.
                Note: Via the use of JORM, Speedo only supports primitive fields as key of the map. Moreover, the key of an element of a map is always loaded. This means that, even if the #key is                 not specified in the fetch group, the key will be loaded.

    <class name="Node" ...>
    ...
    <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>
               If the "keyValue" fetch group is active, both keys and values of elements in the map of the node object will be loaded.
               If the "keyOnly" fetch group is active, only keys of elements in the map of the node object will be loaded. The value field is set to null for each element of the map.

    Managing fetch groups

        Fetch groups are activated using methods of the FetchPlan interface. The mutating methods of this interface return the FetchPlan instance to allow method chaining.
        Once defined in the jdo files, fetch groups can be activated in the persistence manager at runtime as described below:
    1. Get the fetch plan from the persistence manager. The fetch plan contains the "default" fetch group. A fetch plan can contain many fetch groups.
    2. Add and remove the fetch group(s) to define the fetch plan wanted.
    PersistenceManager pm = pmf.getPersistenceManager();
    FetchPlan fp = pm.getFetchPlan();
    fp.addGroup("detail").removeGroup("default"); //only the detail fg is active
        The application is then ready to detach, refresh and retrieve instances using the fetch groups.
        Note: if no fetch plan has been defined for the persistence manager, the default fetch group is used.
       

  5. JDO queries
  6. 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.


    Specifying the result of the query

    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:

    Specifying the Class of the Result

    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.

    Example: single result with navigational 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();

    Example: single result with field

    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();

    Example: single result with aggregate

    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();

    Example: single result with navigational 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();

    Example: Composite result encapsulated into a user class

    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();

    Example: Composite result without user class

    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();

    Grouping Aggregate Results

    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.

    Example: Compute the salaray average by department name

    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();

    Limiting the Cardinality:

    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.

    Select 50 employees ordered by the 'name'

    Query query = pm.newQuery(Employee.class);
    query.setRange(50,10O);
    List l = (List) query.execute();
    assertTrue(50 >= l.size());

  7. Supported types
  8. Speedo supports the following types, which means that a persistent field can be declared with one of these types:


  9. Delete cascade
  10. 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 :