5.  Persistent Fields

5.1. Restoring State
5.2. Typing and Ordering
5.3. Calendar Fields and TimeZones
5.4. Proxies
5.4.1. Smart Proxies
5.4.2. Large Result Set Proxies
5.4.3. Custom Proxies
5.5. Externalization
5.5.1. External Values

OpenJPA enhances the specification's support for persistent fields in many ways. This section documents aspects of OpenJPA's persistent field handling that may affect the way you design your persistent classes.

5.1.  Restoring State

While the JPA specification says that you should not use rolled back objects, such objects are perfectly valid in OpenJPA. You can control whether the objects' managed state is rolled back to its pre-transaction values with the openjpa.RestoreState configuration property. none does not roll back state (the object becomes hollow, and will re-load its state the next time it is accessed), immutable restores immutable values (primitives, primitive wrappers, strings) and clears mutable values so that they are reloaded on next access, and all restores all managed values to their pre-transaction state.

5.2.  Typing and Ordering

When loading data into a field, OpenJPA examines the value you assign the field in your declaration code or in your no-args constructor. If the field value's type is more specific than the field's declared type, OpenJPA uses the value type to hold the loaded data. OpenJPA also uses the comparator you've initialized the field with, if any. Therefore, you can use custom comparators on your persistent field simply by setting up the comparator and using it in your field's initial value.

Example 5.11.  Using Initial Field Values

Though the annotations are left out for simplicity, assume employeesBySal and departments are persistent fields in the class below.

public class Company {

    // OpenJPA will detect the custom comparator in the initial field value
    // and use it whenever loading data from the database into this field
    private Collection employeesBySal = new TreeSet(new SalaryComparator());
    private Map departments;

    public Company {
        // or we can initialize fields in our no-args constructor; even though
        // this field is declared type Map, OpenJPA will detect that it's 
        // actually a TreeMap and use natural ordering for loaded data
        departments = new TreeMap();
    }

    // rest of class definition...
}

5.3.  Calendar Fields and TimeZones

OpenJPA's support for the java.util.Calendar type will store only the Date part of the field, not the TimeZone associated with the field. When loading the date into the Calendar field, OpenJPA will use the TimeZone that was used to initialize the field.

Note

OpenJPA will automatically track changes made via modification methods in fields of type Calendar, with one exception: when using Java version 1.3, the set() method cannot be overridden, so when altering the calendar using that method, the field must be explicitly marked as dirty. This limitation does not apply when running with Java version 1.4 and higer.

5.4.  Proxies

At runtime, the values of all mutable second class object fields in persistent and transactional objects are replaced with implementation-specific proxies. On modification, these proxies notify their owning instance that they have been changed, so that the appropriate updates can be made on the datastore.

5.4.1.  Smart Proxies

Most proxies only track whether or not they have been modified. Smart proxies for collection and map fields, however, keep a record of which elements have been added, removed, and changed. This record enables the OpenJPA runtime to make more efficient database updates on these fields.

When designing your persistent classes, keep in mind that you can optimize for OpenJPA smart proxies by using fields of type java.util.Set , java.util.TreeSet, and java.util.HashSet for your collections whenever possible. Smart proxies for these types are more efficient than proxies for Lists. You can also design your own smart proxies to further optimize OpenJPA for your usage patterns. See the section on custom proxies for details.

5.4.2.  Large Result Set Proxies

Under standard ORM behavior, traversing a persistent collection or map field brings the entire contents of that field into memory. Some persistent fields, however, might represent huge amounts of data, to the point that attempting to fully instantiate them can overwhelm the JVM or seriously degrade performance.

OpenJPA uses special proxy types to represent these "large result set" fields. OpenJPA's large result set proxies do not cache any data in memory. Instead, each operation on the proxy offloads the work to the database and returns the proper result. For example, the contains method of a large result set collection will perform a SELECT COUNT(*) query with the proper WHERE conditions to find out if the given element exists in the database's record of the collection. Similarly, each time you obtain an iterator OpenJPA performs the proper query using the current large result set settings, as discussed in the JDBC chapter. As you invoke Iterator.next, OpenJPA instantiates the result objects on-demand.

You can free the resources used by a large result set iterator by passing it to the static OpenJPAPersistence.close method.

Example 5.12.  Using a Large Result Set Iterator

import org.apache.openjpa.persistence.*;

...

Collection employees = company.getEmployees(); // employees is a lrs collection
Iterator itr = employees.iterator();
while (itr.hasNext())
    process((Employee) itr.next());
OpenJPAPersistence.close(itr);

You can also add and remove from large result set proxies, just as with standard fields. OpenJPA keeps a record of all changes to the elements of the proxy, which it uses to make sure the proper results are always returned from collection and map methods, and to update the field's database record on commit.

In order to use large result set proxies in JPA, add the org.apache.openjpa.persistence.LRS annotation to the persistent field.

The following restrictions apply to large result set fields:

  • The field must be declared as either a java.util.Collection or java.util.Map. It cannot be declared as any other type, including any sub-interface of collection or map, or any concrete collection or map class.

  • The field cannot have an externalizer (see Section 5.5, “ Externalization ”).

  • Because they rely on their owning object for context, large result set proxies cannot be transferred from one persistent field to another. The following code would result in an error on commit:

    Collection employees = company.getEmployees()  // employees is a lrs collection
    company.setEmployees(null);
    anotherCompany.setEmployees(employees);
    

Example 5.13.  Marking a Large Result Set Field

import org.apache.openjpa.persistence.*;

@Entity
public class Company {

    @ManyToMany
    @LRS private Collection<Employee> employees;

    ...
}

5.4.3.  Custom Proxies

OpenJPA manages proxies through the org.apache.openjpa.util.ProxyManager interface. OpenJPA includes a default proxy manager, the org.apache.openjpa.util.ProxyManagerImpl (with a plugin alias name of default), that will meet the needs of most users. The default proxy manager understands the following configuration properties:

  • TrackChanges: Whether to use smart proxies. Defaults to true.

  • AssertAllowedType: Whether to immediately throw an exception if you attempt to add an element to a collection or map that is not assignable to the element type declared in metadata. Defaults to false.

The default proxy manager can proxy the standard methods of any Collection, List, Map, Queue, Date, or Calendar class, including custom implementations. It can also proxy custom classes whose accessor and mutator methods follow JavaBean naming conventions. Your custom types must, however, meet the following criteria:

  • Custom container types must have a public no-arg constructor or a public constructor that takes a single Comparator parameter.

  • Custom date types must have a public no-arg constructor or a public constructor that takes a single long parameter representing the current time.

  • Other custom types must have a public no-arg constructor or a public copy constructor. If a custom types does not have a copy constructor, it must be possible to fully copy an instance A by creating a new instance B and calling each of B's setters with the value from the corresponding getter on A.

If you have custom classes that must be proxied and do not meet these requirements, OpenJPA allows you to define your own proxy classes and your own proxy manager. See the openjpa.util package Javadoc for details on the interfaces involved, and the utility classes OpenJPA provides to assist you.

You can plug your custom proxy manager into the OpenJPA runtime through the openjpa.ProxyManager configuration property.

Example 5.14.  Configuring the Proxy Manager

<property name="openjpa.ProxyManager" value="TrackChanges=false"/>

5.5.  Externalization

OpenJPA offers the ability to write custom field mappings in order to have complete control over the mechanism with which fields are stored, queried, and loaded from the datastore. Often, however, a custom mapping is overkill. There is often a simple transformation from a Java field value to its database representation. Thus, OpenJPA provides the externalization service. Externalization allows you to specify methods that will externalize a field value to its database equivalent on store and then rebuild the value from its externalized form on load.

Note

Fields of embeddable classes used for @EmbeddedId values in JPA cannot have externalizers.

The OpenJPA org.apache.openjpa.persistence.Externalizer annotation sets the name of a method that will be invoked to convert the field into its external form for database storage. You can specify either the name of a non-static method, which will be invoked on the field value, or a static method, which will be invoked with the field value as a parameter. Each method can also take an optional StoreContext parameter for access to a persistence context. The return value of the method is the field's external form. By default, OpenJPA assumes that all named methods belong to the field value's class (or its superclasses). You can, however, specify static methods of other classes using the format <class-name>.<method-name>.

Given a field of type CustomType that externalizes to a string, the table below demonstrates several possible externalizer methods and their corresponding metadata extensions.

Table 5.1.  Externalizer Options

Method Extension
public String CustomType.toString() @Externalizer("toString")
public String CustomType.toString(StoreContext ctx) @Externalizer("toString")
public static String AnyClass.toString(CustomType ct) @Externalizer("AnyClass.toString")
public static String AnyClass.toString(CustomType ct, StoreContext ctx) @Externalizer("AnyClass.toString")

The OpenJPA org.apache.openjpa.persistence.Factory annotation contains the name of a method that will be invoked to instantiate the field from the external form stored in the database. Specify a static method name. The method will will be invoked with the externalized value and must return an instance of the field type. The method can also take an optional StoreContext parameter for access to a persistence context. If a factory is not specified, OpenJPA will use the constructor of the field type that takes a single argument of the external type, or will throw an exception if no constructor with that signature exists.

Given a field of type CustomType that externalizes to a string, the table below demonstrates several possible factory methods and their corresponding metadata extensions.

Table 5.2.  Factory Options

Method Extension
public CustomType(String str) none
public static CustomType CustomType.fromString(String str) @Factory("fromString")
public static CustomType CustomType.fromString(String str, StoreContext ctx) @Factory("fromString")
public static CustomType AnyClass.fromString(String str) @Factory("AnyClass.fromString")
public static CustomType AnyClass.fromString(String str, StoreContext ctx) @Factory("AnyClass.fromString")

If your externalized field is not a standard persistent type, you must explicitly mark it persistent. In OpenJPA, you can force a persistent field by annotating it with org.apache.openjpa.persistence.Persistent annotation.

Note

If your custom field type is mutable and is not a standard collection, map, or date class, OpenJPA will not be able to detect changes to the field. You must mark the field dirty manually, or create a custom field proxy. See OpenJPAEntityManager.dirty for how to mark a field dirty manually in JPA. See Section 5.4, “ Proxies ” for a discussion of proxies.

You can externalize a field to virtually any value that is supported by OpenJPA's field mappings (embedded relations are the exception; you must declare your field to be a persistence-capable type in order to embed it). This means that a field can externalize to something as simple as a primitive, something as complex as a collection or map of entities, or anything in between. If you do choose to externalize to a collection or map, OpenJPA recognizes a family of metadata extensions for specying type information for the externalized form of your fields - see Section 3.2.6, “ Type ”. If the external form of your field is an entity object or contains entities, OpenJPA will correctly include the objects in its persistence-by-reachability algorithms and its delete-dependent algorithms.

The example below demonstrates a few forms of externalization.

Example 5.15.  Using Externalization

import org.apache.openjpa.persistence.*;

@Entity
public class Magazine {

    // use Class.getName and Class.forName to go to/from strings
    @Persistent
    @Externalizer("getName")
    @Factory("forName")
    private Class cls;

    // use URL.getExternalForm for externalization. no factory;
    // we can rely on the URL string constructor
    @Persistent
    @Externalizer("toExternalForm")
    private URL url;

    // use our custom methods
    @Persistent
    @Externalizer("Magazine.authorsFromCustomType")
    @Factory("Magazine.authorsToCustomType")
    @ElementType(Author.class)
    private CustomType customType;

    public static Collection authorsFromCustomType(CustomType customType) {
        ... logic to pack custom type into a list of authors ...
    }

    public static CustomType authorsToCustomType (Collection authors) {
        ... logic to create custom type from a collection of authors ...
    }

    ...
}

You can query externalized fields using parameters. Pass in a value of the field type when executing the query. OpenJPA will externalize the parameter using the externalizer method named in your metadata, and compare the externalized parameter with the value stored in the database. As a shortcut, OpenJPA also allows you to use parameters or literals of the field's externalized type in queries, as demonstrated in the example below.

Note

Currently, queries are limited to fields that externalize to a primitive, primitive wrapper, string, or date types, due to constraints on query syntax.

Example 5.16.  Querying Externalization Fields

Assume the Magazine class has the same fields as in the previous example.

// you can query using parameters
Query q = em.createQuery("select m from Magazine m where m.url = :u");
q.setParameter("u", new URL("http://www.solarmetric.com"));
List results = q.getResultList();

// or as a shortcut, you can use the externalized form directly
q = em.createQuery("select m from Magazine m where m.url = 'http://www.solarmetric.com'");
results = q.getResultList();

5.5.1.  External Values

Externalization often takes simple constant values and transforms them to constant values of a different type. An example would be storing a boolean field as a char, where true and false would be stored in the database as 'T' and 'F' respectively.

OpenJPA allows you to define these simple translations in metadata, so that the field behaves as in full-fledged externalization without requiring externalizer and factory methods. External values supports translation of pre-defined simple types (primitives, primitive wrappers, and Strings), to other pre-defined simple values.

Use the OpenJPA org.apache.openjpa.persistence.ExternalValues annotation to define external value translations. The values are defined in a format similar to that of configuration plugins, except that the value pairs represent Java and datastore values. To convert the Java boolean values of true and false to the character values T and F, for example, you would use the extension value: true=T,false=F.

If the type of the datastore value is different from the field's type, use the org.apache.openjpa.persistence.Type annotation to define the datastore type.

Example 5.17.  Using External Values

This example uses external value translation to transform a string field to an integer in the database.

public class Magazine {

    @ExternalValues({"SMALL=5", "MEDIUM=8", "LARGE=10"})
    @Type(int.class)
    private String sizeWidth;

    ...
}