Persistent Classes
persistent classes
Persistent class basics are covered in
of the JPA Overview. This chapter details the persistent class features OpenJPA
offers beyond the core JPA specification.
Persistent Class List
persistent classes
list
PCClasses
Unlike many ORM products, OpenJPA does not need to know about all of your
persistent classes at startup. OpenJPA discovers new persistent classes
automatically as they are loaded into the JVM; in fact you can introduce new
persistent classes into running applications under OpenJPA. However, there are
certain situations in which providing OpenJPA with a persistent class list is
helpful:
OpenJPA must be able to match entity names in JPQL queries to persistent
classes. OpenJPA automatically knows the entity names of any persistent classes
already loaded into the JVM. To match entity names to classes that have not been
loaded, however, you must supply a persistent class list.
When OpenJPA manipulates classes in a persistent inheritance hierarchy, OpenJPA
must be aware of all the classes in the hierarchy. If some of the classes have
not been loaded into the JVM yet, OpenJPA may not know about them, and queries
may return incorrect results.
If you configure OpenJPA to create the needed database schema on startup (see
), OpenJPA must know all of your
persistent classes up-front.
When any of these conditions are a factor in your JPA application, use the
class, mapping-file, and
jar-file elements of JPA's standard XML format to list your persistent
classes. See for details.
Alternately, you can tell OpenJPA to search through your classpath for
persistent types. This is described in more detail in
.
Listing persistent classes (or their metadata or jar files) is an all-or-nothing
endeavor. If your persistent class list is non-empty, OpenJPA will assume that
any unlisted class is not persistent.
Enhancement
enhancer
openjpac
enhancer
In order to provide optimal runtime performance, flexible lazy loading, and
efficient, immediate dirty tracking, OpenJPA can use an enhancer
. An enhancer is a tool that automatically adds code to your
persistent classes after you have written them. The enhancer post-processes the
bytecode generated by your Java compiler, adding the necessary fields and
methods to implement the required persistence features. This bytecode
modification perfectly preserves the line numbers in stack traces and is
compatible with Java debuggers. In fact, the only change to debugging
is that the persistent setter and getter methods of entity classes using
property access will be prefixed with pc in stack traces and
step-throughs. For example, if your entity has a getId
method for persistent property id, and that method throws an
exception, the stack trace will report the exception from method
pcgetId. The line numbers, however, will correctly correspond to
the getId method in your source file.
The diagram above illustrates the compilation of a persistent class.
You can add the OpenJPA enhancer to your build process, or use Java 1.5's
instrumentation features to transparently enhance persistent classes when they
are loaded into the JVM. The following sections describe each option.
Enhancing at Build Time
enhancer
build time
The enhancer can be invoked at build time
via its Java class,
org.apache.openjpa.enhance.PCEnhancer.
You can also enhance via Ant; see
.
Using the OpenJPA Enhancer
java org.apache.openjpa.enhance.PCEnhancer Magazine.java
The enhancer accepts the standard set of command-line arguments defined by the
configuration framework (see ),
along with the following flags:
-directory/-d <output directory>: Path to the output
directory. If the directory does not match the enhanced class' package, the
package structure will be created beneath the directory. By default, the
enhancer overwrites the original .class file.
-enforcePropertyRestrictions/-epr <true/t | false/f>:
Whether to throw an exception when it appears that a property access entity is
not obeying the restrictions placed on property access. Defaults to false.
-addDefaultConstructor/-adc <true/t | false/f>: The
spec requires that all persistent classes define a no-arg constructor. This flag
tells the enhancer whether to add a protected no-arg constructor to any
persistent classes that don't already have one. Defaults to
true.
-tmpClassLoader/-tcl <true/t | false/f>: Whether to
load persistent classes with a temporary class loader. This allows other code to
then load the enhanced version of the class within the same JVM. Defaults to
true. Try setting this flag to false as a
debugging step if you run into class loading problems when running the enhancer.
Each additional argument to the enhancer must be one of the following:
The full name of a class.
The .java file for a class.
The .class file of a class.
If you do not supply any arguments to the enhancer, it will run on the classes
in your persistent class list (see ).
You can run the enhancer over classes that have already been enhanced, in which
case it will not further modify the class. You can also run it over classes that
are not persistence-capable, in which case it will treat the class as
persistence-aware. Persistence-aware classes can directly manipulate the
persistent fields of persistence-capable classes.
Note that the enhancement process for subclasses introduces dependencies on the
persistent parent class being enhanced. This is normally not problematic;
however, when running the enhancer multiple times over a subclass whose parent
class is not yet enhanced, class loading errors can occur. In the event of a
class load error, simply re-compile and re-enhance the offending classes.
Enhancing JPA Entities on Deployment
enhancer
runtime
in an EJB container
The Java EE 5 specification includes hooks to automatically enhance JPA entities
when they are deployed into a container. Thus, if you are using a Java EE
5-compliant application server, OpenJPA will enhance your entities automatically
at runtime. Note that if you prefer build-time enhancement, OpenJPA's runtime
enhancer will correctly recognize and skip pre-enhanced classes.
If your application server does not support the Java EE 5 enhancement hooks,
consider using the build-time enhancement described above, or the more general
runtime enhancement described in the next section.
Enhancing at Runtime
enhancer
runtime
outside a container
OpenJPA includes a Java agent for automatically enhancing
persistent classes as they are loaded into the JVM. Java agents are classes that
are invoked prior to your application's main method.
OpenJPA's agent uses JVM hooks to intercept all class loading to enhance classes
that have persistence metadata before the JVM loads them.
Searching for metadata for every class loaded by the JVM can slow application
initialization. One way to speed things up is to take advantage of the optional
persistent class list described in . If
you declare a persistent class list, OpenJPA will only search for
metadata for classes in that list.
To employ the OpenJPA agent, invoke java with the
-javaagent set to the path to your OpenJPA jar file.
Using the OpenJPA Agent for Runtime Enhancement
java -javaagent:/home/dev/openjpa/lib/openjpa.jar com.xyz.Main
You can pass settings to the agent using OpenJPA's plugin syntax (see
). The agent accepts the long
form of any of the standard configuration options
( ). It also accepts the following
options, the first three of which correspond exactly to to the same-named
options of the enhancer tool described in
:
addDefaultConstructorenforcePropertyRestrictionsscanDevPath: Boolean indicating whether to scan the
classpath for persistent types if none have been configured. If you do not
specify a persistent types list and do not set this option to true, OpenJPA will
check whether each class loaded into the JVM is persistent, and enhance it
accordingly. This may slow down class load times significantly.
classLoadEnhancement: Boolean controlling whether OpenJPA
load-time class enhancement should be available in this JVM execution. Default:
trueruntimeRedefinition: Boolean controlling whether OpenJPA
class redefinition should be available in this JVM execution. Default:
true
Passing Options to the OpenJPA Agent
java -javaagent:/home/dev/openjpa/lib/openjpa.jar=addDefaultConstructor=false com.xyz.Main
Omitting the OpenJPA enhancer
enhancer
omitting
outside a container
OpenJPA does not require that the enhancer be run. If you do not run the
enhancer, OpenJPA will fall back to one of several possible alternatives for
state tracking, depending on the execution environment.
Deploy-time enhancement: if you are running your
application inside a Java EE 5 container, or another environment that supports
the JPA container contract, then OpenJPA will automatically perform class
transformation at deploy time.
Java 6 class retransformation: if you are running your
application in a Java 6 environment, OpenJPA will attempt to dynamically
register a ClassTransformer that will redefine your
persistent classes on the fly to track access to persistent data. Additionally,
OpenJPA will create a subclass for each of your persistent classes. When
you execute a query or traverse a relation, OpenJPA will return an instance
of the subclass. This means that the instanceof operator
will work as expected, but o.getClass() will return the
subclass instead of the class that you wrote.
You do not need to do anything at all to get this behavior. OpenJPA will
automatically detect whether or not the execution environment is capable of
Java 6 class retransformation.
Java 5 class redefinition: if you are running your
application in a Java 5 environment, and you specify the OpenJPA javaagent,
OpenJPA will use Java 5 class redefinition to redefine any persistent classes
that are not enhanced by the OpenJPA javaagent. Aside from the requirement
that you specify a javaagent on the command line, this behavior is exactly the
same as the Java 6 class retransformation behavior. Of course, since the
OpenJPA javaagent performs enhancement by default, this will only be available
if you set the classLoadEnhancement javaagent flag to
false, or on any classes that are skipped by the OpenJPA
runtime enhancement process for some reason.
state comparison and subclassing: if you are running
in a Java 5 environment without a javaagent, or in a Java 6 environment that
does not support class retransformation, OpenJPA will still create subclasses
as outlined above. However, in some cases, OpenJPA may not be able to receive
notifications when you read or write persistent data.
If you are using property access for your persistent data,
then OpenJPA will be able to track all accesses for instances that you load
from the database, but not for instances that you create. This is because
OpenJPA will create new instances of its dynamically-generated subclass when
it loads data from the database. The dynamically-generated subclass has
code in the setters and getters that notify OpenJPA about persistent data
access. This means that new instances that you create will be subject to
state-comparison checks (see discussion below) to compute which fields to
write to the database, and that OpenJPA will ignore requests to evict
persistent data from such instances. In practice, this is not a particularly
bad limitation, since OpenJPA already knows that it must insert all field
values for new instances. So, this is only really an issue if you flush
changes to the database while inserting new records; after such a flush,
OpenJPA will need to hold potentially-unneeded hard references to the
new-flushed instances.
If you are using field access for your persistent data,
then OpenJPA will not be able to track accesses for any instances, including
ones that you load from the database. So, OpenJPA will perform state-comparison
checks to determine which fields are dirty. These state comparison checks are
costly in two ways. First, there is a performance penalty at flush / commit
time, since OpenJPA must walk through every field of every instance to determine
which fields of which records are dirty. Second, there is a memory penalty,
since OpenJPA must hold hard references to all instances that were loaded at
any time in a given transaction, and since OpenJPA must keep a copy of all
the initial values of the loaded data for later comparison. Additionally,
OpenJPA will ignore requests to evict persistent state for these types of
instances. Finally, the default lazy loading configuration will be ignored for
single-valued fields (one-to-one, many-to-one, and any other non-collection
or non-map field that has a lazy loading configuration). If you use fetch
groups or programmatically configure your fetch plan, OpenJPA will obey these
directives, but will be unable to lazily load any data that you exclude from
loading. As a result of these limitations, it is not recommended that you use
field access if you are not either running the enhancer or using OpenJPA with
a javaagent or in a Java 6 environment.
Managed Interfacesinterfacesmanaged
OpenJPA's managed interface feature allows you to define your object model
entirely in terms of interfaces, instead of concrete classes. To use this
feature, you must annotate your managed interfaces with the
ManagedInterface annotation, and use the
OpenJPAEntityManager.createInstance(Class) method to
create new records. Note that createInstance() returns
unmanaged instances; you must pass them to
EntityManager.persist() to store them in the database.
@ManagedInterface
public interface PersonIface {
@Id @GeneratedValue
int getId();
void setId(int id);
// implicitly persistent per JPA property rules
String getName();
void setName(String name);
}
OpenJPAEntityManager em = ...;
PersonIface person = em.createInstance(PersonIface.class);
person.setName("Homer Simpson");
em.getTransaction().begin();
em.persist(person);
em.getTransaction().commit();
Object Identity
identity
OpenJPA makes several enhancements to JPA's standard entity identity.
Datastore Identity
identity
datastore
The JPA specification requires you to declare one or more identity fields in
your persistent classes. OpenJPA fully supports this form of object identity,
called application identity. OpenJPA, however, also
supports datastore identity. In datastore identity, you do
not declare any primary key fields. OpenJPA manages the identity of your
persistent objects for you through a surrogate key in the database.
You can control how your JPA datastore identity value is generated through
OpenJPA's
org.apache.openjpa.persistence.DataStoreId class
annotation. This annotation has strategy and
generator properties that mirror the same-named properties on the
standard javax.persistence.GeneratedValue annotation
described in of the JPA Overview.
To retrieve the identity value of a datastore identity entity, use the
OpenJPAEntityManager.getObjectId(Object entity)
method. See for more information on
the OpenJPAEntityManager.
JPA Datastore Identity Metadata
import org.apache.openjpa.persistence.*;
@Entity
@DataStoreId
public class LineItem {
... no @Id fields declared ...
}
Internally, OpenJPA uses the public
org.apache.openjpa.util.Id class for datastore identity
objects. When writing OpenJPA plugins, you can manipulate datastore identity
objects by casting them to this class. You can also create your own
Id instances and pass them to any internal OpenJPA method that
expects an identity object.
In JPA, you will never see Id instances directly.
Instead, calling OpenJPAEntityManager.getObjectId on a
datastore identity object will return the Long surrogate
primary key value for that object. You can then use this value in calls to
EntityManager.find for subsequent lookups of the same
record.
Entities as Identity Fields
identity
application
entity id fields
The JPA specification limits identity fields to simple types. OpenJPA, however,
also allows ManyToOne and OneToOne
relations to be identity fields. To identify a relation field as an identity
field, simply annotate it with both the @ManyToOne or
@OneToOne relation annotation and the @Id
identity annotation.
When finding an entity identified by a relation, pass the id of the
relation to the EntityManager.find
method:
Finding an Entity with an Entity Identity Field
public Delivery createDelivery(EntityManager em, Order order) {
Delivery delivery = new Delivery();
delivery.setId(o);
delivery.setDelivered(new Date());
return delivery;
}
public Delivery findDelivery(EntityManager em, Order order) {
// use the identity of the related instance
return em.find(Delivery.class, order.getId());
}
When your entity has multiple identity fields, at least one of which is a
relation to another entity, you must use an identity class (see
in the JPA Overview). You cannot
use an embedded identity object. Identity class fields corresponding to
entity identity fields should be of the same type as the related entity's
identity.
Id Class for Entity Identity Fields
@Entity
public class Order {
@Id
private long id;
...
}
@Entity
@IdClass(LineItemId.class)
public class LineItem {
@Id
private int index;
@Id
@ManyToOne
private Order order;
...
}
public class LineItemId {
public int index;
public long order; // same type as order's identity
...
}
In the example above, if Order had used an identity
class OrderId in place of a simple long
value, then the LineItemId.order field would
have been of type OrderId.
Application Identity Tool
identity
application
application identity tool
application identity tool
If you choose to use application identity, you may want to take advantage of
OpenJPA's application identity tool. The application
identity tool generates Java code implementing the identity class for any
persistent type using application identity. The code satisfies all the
requirements the specification places on identity classes. You can use it as-is,
or simply use it as a starting point, editing it to meet your needs.
Before you can run the application identity tool on a persistent class, the
class must be compiled and must have complete metadata. All primary key fields
must be marked as such in the metadata.
In JPA metadata, do not attempt to specify the @IdClass
annotation unless you are using the application identity tool to overwrite an
existing identity class. Attempting to set the value of the @IdClass
to a non-existent class will prevent your persistent class from
compiling. Instead, use the -name or -suffix
options described below to tell OpenJPA what name to give your
generated identity class. Once the application identity tool has generated the
class code, you can set the @IdClass annotation.
The application identity tool can be invoked via its Java class,
org.apache.openjpa.enhance.ApplicationIdTool.
describes the
application identity tool's Ant task.
Using the Application Identity Tool
java org.apache.openjpa.enhance.ApplicationIdTool -s Id Magazine.java
The application identity tool accepts the standard set of command-line arguments
defined by the configuration framework (see
), including code formatting
flags described in . It
also accepts the following arguments:
-directory/-d <output directory>: Path to the output
directory. If the directory does not match the generated oid class' package, the
package structure will be created beneath the directory. If not specified, the
tool will first try to find the directory of the .java file
for the persistence-capable class, and failing that will use the current
directory.
-ignoreErrors/-i <true/t | false/f>: If false
, an exception will be thrown if the tool is run on any class that
does not use application identity, or is not the base class in the inheritance
hierarchy (recall that subclasses never define the application identity class;
they inherit it from their persistent superclass).
-token/-t <token>: The token to use to separate
stringified primary key values in the string form of the object id. This option
is only used if you have multiple primary key fields. It defaults to "::".
-name/-n <id class name>: The name of the identity
class to generate. If this option is specified, you must run the tool on exactly
one class. If the class metadata already names an object id class, this option
is ignored. If the name is not fully qualified, the persistent class' package is
prepended to form the qualified name.
-suffix/-s <id class suffix>: A string to suffix each
persistent class name with to form the identity class name. This option is
overridden by -name or by any object id class specified in
metadata.
Each additional argument to the tool must be one of the following:
The full name of a persistent class.
The .java file for a persistent class.
The .class file of a persistent class.
If you do not supply any arguments to the tool, it will act on the classes in
your persistent classes list (see ).
Autoassign / Identity Strategy Caveats
datastore identity
autoassign strategy
datastore identity
autoassign strategy
persistent fields
autoassign strategy
explains how to use JPA's
IDENTITY generation type to automatically assign field
values. However, here are some additional caveats you should be aware of when
using IDENTITY generation:
Your database must support auto-increment / identity columns, or some equivalent
(see for how to
configure a combination of triggers and sequences to fake auto-increment support
in Oracle).
Auto-increment / identity columns must be an integer or long integer type.
Databases support auto-increment / identity columns to varying degrees. Some do
not support them at all. Others only allow a single such column per table, and
require that it be the primary key column. More lenient databases may allow
non-primary key auto-increment columns, and may allow more than one per table.
See your database documentation for details.
Managed Inverses
bidirectional relations
automatic management
Bidirectional relations are an essential part of data modeling.
in the JPA Overview explains how to
use the mappedBy annotation attribute to form bidirectional
relations that also share datastore storage in JPA.
OpenJPA also allows you to define purely logical bidirectional relations. The
org.apache.openjpa.persistence.InverseLogical
annotation names a logical inverse in JPA metadata.
Specifying Logical Inverses
Magazine.coverPhoto and Photograph.mag are
each mapped to different foreign keys in their respective tables, but form a
logical bidirectional relation. Only one of the fields needs to declare the
other as its logical inverse, though it is not an error to set the logical
inverse of both fields.
import org.apache.openjpa.persistence.*;
@Entity
public class Magazine {
@OneToOne
private Photograph coverPhoto;
...
}
@Entity
public class Photograph {
@OneToOne
@InverseLogical("coverPhoto")
private Magazine mag;
...
}
Java does not provide any native facilities to ensure that both sides of a
bidirectional relation remain consistent. Whenever you set one side of the
relation, you must manually set the other side as well.
By default, OpenJPA behaves the same way. OpenJPA does not automatically
propagate changes from one field in bidirectional relation to the other field.
This is in keeping with the philosophy of transparency, and also provides higher
performance, as OpenJPA does not need to analyze your object graph to correct
inconsistent relations.
InverseManager
If convenience is more important to you than strict transparency, however, you
can enable inverse relation management in OpenJPA. Set the
openjpa.InverseManager
plugin property to true for standard
management. Under this setting, OpenJPA detects changes to either side of a
bidirectional relation (logical or physical), and automatically sets the other
side appropriately on flush.
Enabling Managed Inverses
<property name="openjpa.InverseManager" value="true"/>
The inverse manager has options to log a warning or throw an exception when it
detects an inconsistent bidirectional relation, rather than correcting it. To
use these modes, set the manager's Action property to
warn or exception, respectively.
By default, OpenJPA excludes large
result set fields from management. You can force large result set fields
to be included by setting the ManageLRS plugin property to
true.
Log Inconsistencies
<property name="openjpa.InverseManager" value="true(Action=warn)"/>
Persistent Fields
persistent fields
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.
Restoring State
persistent fields
field rollback
RestoreState
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.
Typing and Ordering
persistent fields
comparators
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.
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...
}
Calendar Fields and TimeZones
persistent fields
calendar
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.
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.
Proxies
proxies
persistent fields
proxies
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.
Smart Proxies
proxies
smart
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.
Large Result Set Proxies
proxies
large result set
large result sets
fields
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.
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
).
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);
Marking a Large Result Set Field
import org.apache.openjpa.persistence.*;
@Entity
public class Company {
@ManyToMany
@LRS private Collection<Employee> employees;
...
}
Custom Proxies
proxies
custom
proxies
ProxyManager
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.
Configuring the Proxy Manager
<property name="openjpa.ProxyManager" value="TrackChanges=false"/>
Externalization
externalization
persistent fields
externalization
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.
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.
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 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.
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.
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 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 . 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.
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 ...
}
...
}
externalization
queries
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.
Currently, queries are limited to fields that externalize to a primitive,
primitive wrapper, string, or date types, due to constraints on query syntax.
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();
External Values
externalization
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.
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;
...
}
Fetch Groups
fetch groups
Fetch groups are sets of fields that load together. They can be used to to pool
together associated fields in order to provide performance improvements over
standard data fetching. Specifying fetch groups allows for tuning of lazy
loading and eager fetching behavior.
The JPA Overview's describes how
to use JPA metadata annotations to control whether a field is fetched eagerly or
lazily. Fetch groups add a dynamic aspect to this standard ability. As you will
see, OpenJPA's JPA extensions allow you can add and remove fetch groups at
runtime to vary the sets of fields that are eagerly loaded.
Custom Fetch Groups
OpenJPA places any field that is eagerly loaded according to the JPA metadata
rules into the built-in default fetch group. As its name
implies, the default fetch group is active by default. You may also
define your own named fetch groups and activate or deactivate them at runtime,
as described later in this chapter. OpenJPA will eagerly-load the fields in all
active fetch groups when loading objects from the datastore.
You create fetch groups with the
org.apache.openjpa.persistence.FetchGroup
annotation. If your class only has one custom fetch group, you can place this
annotation directly on the class declaration. Otherwise, use the
org.apache.openjpa.persistence.FetchGroups
annotation to declare an array of individual FetchGroup
values. The FetchGroup annotation has the following
properties:
String name: The name of the fetch group. Fetch group names
are global, and are expected to be shared among classes. For example, a shopping
website may use a detail fetch group in each product class
to efficiently load all the data needed to display a product's "detail" page.
The website might also define a sparse list fetch group
containing only the fields needed to display a table of products, as in a search
result.
The following names are reserved for use by OpenJPA: default
, values, all, none,
and any name beginning with jdo, jpa, or
openjpa.
FetchAttribute[] attributes: The set of persistent fields or
properties in the fetch group.
String[] fetchGroups: Other fetch groups whose fields to
include in this group.
As you might expect, listing a
org.apache.openjpa.persistence.FetchAttribute
within a FetchGroup includes the corresponding persistent
field or property in the fetch group. Each FetchAttribute
has the following properties:
String name: The name of the persistent field or property to
include in the fetch group.
recursionDepth: If the attribute represents a relation, the
maximum number of same-typed relations to eager-fetch from this field. Defaults
to 1. For example, consider an Employee class with a
manager field, also of type Employee.
When we load an Employee and the
manager field is in an active fetch group, the recursion depth (along
with the max fetch depth setting, described below) determines whether we only
retrieve the target Employee and his manager (depth 1),
or whether we also retrieve the manager's manager (depth 2), or the manager's
manager's manager (depth 3), etc. Use -1 for unlimited depth.
Custom Fetch Group Metadata
Creates a detail fetch group consisting of the
publisher and articles relations.
import org.apache.openjpa.persistence.*;
@Entity
@FetchGroups({
@FetchGroup(name="detail", attributes={
@FetchAttribute(name="publisher"),
@FetchAttribute(name="articles")
}),
...
})
public class Magazine {
...
}
A field can be a member of any number of fetch groups. A field can also
declare a load fetch group.
When you access a lazy-loaded field for the first time, OpenJPA makes a
datastore trip to fetch that field's data. Sometimes, however, you know
that whenever you access a lazy field A, you're likely to access lazy fields B
and C as well. Therefore, it would be more efficient to fetch the data for A,
B, and C in the same datastore trip. By setting A's load fetch group to the
name of a fetch group containing B and
C, you can tell OpenJPA to load all of these fields together when A is first
accessed.
Use OpenJPA's
org.apache.openjpa.persistence.LoadFetchGroup
annotation to specify the load fetch group of any persistent field. The value of
the annotation is the name of a declared fetch group whose members should be
loaded along with the annotated field.
Load Fetch Group Metadata
import org.apache.openjpa.persistence.*;
@Entity
@FetchGroups({
@FetchGroup(name="detail", attributes={
@FetchAttribute(name="publisher"),
@FetchAttribute(name="articles")
}),
...
})
public class Magazine {
@ManyToOne(fetch=FetchType.LAZY)
@LoadFetchGroup("detail")
private Publisher publisher;
...
}
Custom Fetch Group Configuration
fetch groups
custom configuration
fetch groups
FetchGroups
You can control the default set of fetch groups with the
openjpa.FetchGroups
configuration property. Set this property to a comma-separated list of
fetch group names.
You can also set the system's default maximum fetch depth with the
openjpa.MaxFetchDepth
configuration property. The maximum fetch depth determines how "deep"
into the object graph to traverse when loading an instance. For example, with
a MaxFetchDepth of 1, OpenJPA will load at most the target
instance and its immediate relations. With a MaxFetchDepth
of 2, OpenJPA may load the target instance, its immediate relations, and
the relations of those relations. This works to arbitrary depth. In fact,
the default MaxFetchDepth value is -1, which symbolizes
infinite depth. Under this setting, OpenJPA will fetch configured relations
until it reaches the edges of the object graph. Of course, which relation
fields are loaded depends on whether the fields are eager or lazy, and on the
active fetch groups. A fetch group member's recursion depth may also limit
the fetch depth to something less than the configured maximum.
OpenJPA's OpenJPAEntityManager and
OpenJPAQuery extensions to the standard EntityManager
and Query interfaces provide access to a
org.apache.openjpa.persistence.FetchPlan object.
The FetchPlan maintains the set of active fetch groups
and the maximum fetch depth. It begins with the groups and depth defined in the
openjpa.FetchGroups and openjpa.MaxFetchDepth
properties, but allows you to add or remove groups and change the
maximum fetch depth for an individual EntityManager or
Query through the methods below.
public FetchPlan addFetchGroup(String group);
public FetchPlan addFetchGroups(String... groups);
public FetchPlan addFetchGroups(Collection groups);
public FetchPlan removeFetchGrop(String group);
public FetchPlan removeFetchGroups(String... groups);
public FetchPlan removeFetchGroups(Collection groups);
public FetchPlan resetFetchGroups();
public Collection<String> getFetchGroups();
public void clearFetchGroups();
public FetchPlan setMaxFetchDepth(int depth);
public int getMaxFetchDepth();
details the
OpenJPAEntityManager, OpenJPAQuery, and
FetchPlan interfaces.
Using the FetchPlan
import org.apache.openjpa.persistence.*;
...
OpenJPAQuery kq = OpenJPAPersistence.cast(em.createQuery(...));
kq.getFetchPlan().setMaxFetchDepth(3).addFetchGroup("detail");
List results = kq.getResultList();
Per-field Fetch Configuration
fetch groups
single fields
In addition to controlling fetch configuration on a per-fetch-group basis, you
can configure OpenJPA to include particular fields in the current fetch
plan. This allows you to add individual fields that are not in the
default fetch group or in any other active fetch groups to the set of
fields that will be eagerly loaded from the database.
JPA FetchPlan methods:
public FetchPlan addField(String field);
public FetchPlan addFields(String... fields);
public FetchPlan addFields(Class cls, String... fields);
public FetchPlan addFields(Collection fields);
public FetchPlan addFields(Class cls, Collection fields);
public FetchPlan removeField(String field);
public FetchPlan removeFields(String... fields);
public FetchPlan removeFields(Class cls, String... fields);
public FetchPlan removeFields(Collection fields);
public FetchPlan removeFields(Class cls, Collection fields);
public Collection<String> getFields();
public void clearFields();
The methods that take only string arguments use the fully-qualified field name,
such as org.mag.Magazine.publisher. Similarly,
getFields returns the set of fully-qualified field names. In all
methods, the named field must be defined in the class specified in the
invocation, not a superclass. So, if the field publisher is
defined in base class Publication rather than subclass
Magazine, you must invoke addField
(Publication.class, "publisher") and not addField
(Magazine.class, "publisher"). This is stricter than Java's default
field-masking algorithms, which would allow the latter method behavior if
Magazine did not also define a field called
publisher.
In order to avoid the cost of reflection, OpenJPA does not perform any
validation of the field name / class name pairs that you put into the fetch
configuration. If you specify non-existent class / field pairs, nothing adverse
will happen, but you will receive no notification of the fact that the specified
configuration is not being used.
Adding an Eager Field
import org.apache.openjpa.persistence.*;
...
OpenJPAEntityManager kem = OpenJPAPersistence.cast(em);
kem.getFetchPlan().addField(Magazine.class, "publisher");
Magazine mag = em.find(Magazine.class, magId);
Implementation Notes
Even when a direct relation is not eagerly fetched, OpenJPA selects the foreign
key columns and caches the values. This way when you do traverse the relation,
OpenJPA can often find the related object in its cache, or at least avoid joins
when loading the related object from the database.
The above implicit foreign key-selecting behavior does not always apply when the
relation is in a subclass table. If the subclass table would not otherwise be
joined into the select, OpenJPA avoids the extra join just to select the foreign
key values.
Eager Fetching
eager fetching
persistent fields
eager fetching
fetch groups
eager fetching
eager fetching
lazy loading
eager fetching
fetch groups
Eager fetching is the ability to efficiently load subclass data and related
objects along with the base instances being queried. Typically, OpenJPA has to
make a trip to the database whenever a relation is loaded, or when you first
access data that is mapped to a table other than the least-derived superclass
table. If you perform a query that returns 100 Person
objects, and then you have to retrieve the Address for
each person, OpenJPA may make as many as 101 queries (the initial query, plus
one for the address of each person returned). Or if some of the
Person instances turn out to be Employees,
where Employee has additional data in its own joined
table, OpenJPA once again might need to make extra database trips to access the
additional employee data. With eager fetching, OpenJPA can reduce these cases to
a single query.
Eager fetching only affects relations in the active fetch groups, and is limited
by the declared maximum fetch depth and field recursion depth (see
). In other words, relations that would
not normally be loaded immediately when retrieving an object or accessing a
field are not affected by eager fetching. In our example above, the address of
each person would only be eagerly fetched if the query were configured to
include the address field or its fetch group, or if the address were in the
default fetch group. This allows you to control exactly which fields are eagerly
fetched in different situations. Similarly, queries that exclude subclasses
aren't affected by eager subclass fetching, described below.
Eager fetching has three modes:
none: No eager fetching is performed. Related objects are
always loaded in an independent select statement. No joined subclass data is
loaded unless it is in the table(s) for the base type being queried. Unjoined
subclass data is loaded using separate select statements rather than a SQL UNION
operation.
eager fetching
join mode
join: In this mode, OpenJPA joins to to-one relations in the
configured fetch groups. If OpenJPA is loading data for a single instance, then
OpenJPA will also join to any collection field in the configured fetch groups.
When loading data for multiple instances, though, (such as when executing a
Query) OpenJPA will not join to collections by default.
Instead, OpenJPA defaults to parallel mode for collections,
as described below. You can force OpenJPA use a join rather than parallel mode
for a collection field using the metadata extension described in
.
outer joins
Under join mode, OpenJPA uses a left outer join (or inner
join, if the relations' field metadata declares the relation non-nullable) to
select the related data along with the data for the target objects. This process
works recursively for to-one joins, so that if Person has
an Address, and Address has a
TelephoneNumber, and the fetch groups are configured
correctly, OpenJPA might issue a single select that joins across the tables for
all three classes. To-many joins can not recursively spawn other to-many joins,
but they can spawn recursive to-one joins.
Under the join subclass fetch mode, subclass data in joined
tables is selected by outer joining to all possible subclass tables of the type
being queried. As you'll see below, subclass data fetching is configured
separately from relation fetching, and can be disabled for specific classes.
Some databases may not support outer joins. Also, OpenJPA can not use
outer joins if you have set the
DBDictionary's JoinSyntax to
traditional. See .
eager fetching
parallel mode
parallel: Under this mode, OpenJPA selects to-one relations
and joined collections as outlined in the join mode
description above. Unjoined collection fields, however, are eagerly fetched
using a separate select statement for each collection, executed in parallel with
the select statement for the target objects. The parallel selects use the
WHERE conditions from the primary select, but add their own
joins to reach the related data. Thus, if you perform a query that returns 100
Company objects, where each company has a list of
Employee objects and Department
objects, OpenJPA will make 3 queries. The first will select the company objects,
the second will select the employees for those companies, and the third will
select the departments for the same companies. Just as for joins, this process
can be recursively applied to the objects in the relations being eagerly
fetched. Continuing our example, if the Employee class
had a list of Projects in one of the fetch groups being
loaded, OpenJPA would execute a single additional select in parallel to load the
projects of all employees of the matching companies.
Using an additional select to load each collection avoids transferring more data
than necessary from the database to the application. If eager joins were used
instead of parallel select statements, each collection added to the configured
fetch groups would cause the amount of data being transferred to rise
dangerously, to the point that you could easily overwhelm the network.
Polymorphic to-one relations to table-per-class mappings use parallel eager
fetching because proper joins are impossible. You can force other to-one
relations to use parallel rather than join mode eager fetching using the
metadata extension described in .
Parallel subclass fetch mode only applies to queries on joined inheritance
hierarchies. Rather than outer-joining to
subclass tables, OpenJPA will issue the query separately for each subclass. In
all other situations, parallel subclass fetch mode acts just like join mode in
regards to vertically-mapped subclasses.
When OpenJPA knows that it is selecting for a single object only, it never uses
parallel mode, because the additional selects can be made
lazily just as efficiently. This mode only increases efficiency over
join mode when multiple objects with eager relations are being loaded,
or when multiple selects might be faster than joining to all possible
subclasses.
Configuring Eager Fetching
eager fetching
configuration
EagerFetchMode
SubclassFetchMode
eager fetching
EagerFetchMode
eager fetching
SubclassFetchMode
You can control OpenJPA's default eager fetch mode through the
openjpa.jdbc.EagerFetchMode and
openjpa.jdbc.SubclassFetchMode configuration properties. Set
each of these properties to one of the mode names described in the previous
section: none, join, parallel. If left unset, the eager
fetch mode defaults to parallel and the subclass fetch mode
defaults to join These are generally the most robust and
performant strategies.
You can easily override the default fetch modes at runtime for any lookup or
query through OpenJPA's fetch configuration APIs. See
for details.
Setting the Default Eager Fetch Mode
<property name="openjpa.jdbc.EagerFetchMode" value="parallel"/>
<property name="openjpa.jdbc.SubclassFetchMode" value="join"/>
Setting the Eager Fetch Mode at Runtime
import org.apache.openjpa.persistence.*;
import org.apache.openjpa.persistence.jdbc.*;
...
Query q = em.createQuery("select p from Person p where p.address.state = 'TX'");
OpenJPAQuery kq = OpenJPAPersistence.cast(q);
JDBCFetchPlan fetch = (JDBCFetchPlan) kq.getFetchPlan();
fetch.setEagerFetchMode(FetchMode.PARALLEL);
fetch.setSubclassFetchMode(FetchMode.JOIN);
List results = q.getResultList();
You can specify a default subclass fetch mode for an individual class with the
metadata extension described in .
Note, however, that you cannot "upgrade" the runtime fetch mode with your class
setting. If the runtime fetch mode is none, no eager
subclass data fetching will take place, regardless of your metadata setting.
This applies to the eager fetch mode metadata extension as well (see
). You can use this extension to
disable eager fetching on a field or to declare that a collection would rather
use joins than parallel selects or vice versa. But an extension value of
join won't cause any eager joining if the fetch
configuration's setting is none.
Eager Fetching Considerations and Limitations
There are several important points that you should consider when using eager
fetching:
eager fetching
with large result sets
large result sets
interaction with eager fetching
When you are using parallel eager fetch mode and you have
large result sets enabled (see )
or you place a range on a query, OpenJPA performs the needed parallel selects on
one page of results at a time. For example, suppose your
FetchBatchSize is set to 20, and you perform a large result set query
on a class that has collection fields in the configured fetch groups. OpenJPA
will immediately cache the first 20 results of the query
using join mode eager fetching only. Then, it will issue the
extra selects needed to eager fetch your collection fields according to
parallel mode. Each select will use a SQL IN
clause (or multiple OR clauses if your class has a
compound primary key) to limit the selected collection elements to those owned
by the 20 cached results.
Once you iterate past the first 20 results, OpenJPA will cache the next 20 and
again issue any needed extra selects for collection fields, and so on. This
pattern ensures that you get the benefits of eager fetching without bringing
more data into memory than anticipated.
Once OpenJPA eager-joins into a class, it cannot issue any further eager to-many
joins or parallel selects from that class in the same query. To-one joins,
however, can recurse to any level.
Using a to-many join makes it impossible to determine the number of instances
the result set contains without traversing the entire set. This is because each
result object might be represented by multiple rows. Thus, queries with a range
specification or queries configured for lazy result set traversal automatically
turn off eager to-many joining.
OpenJPA cannot eagerly join to polymorphic relations to non-leaf classes in a
table-per-class inheritance hierarchy. You can work around this restriction
using the mapping extensions described in .