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.
The enhancer can be invoked at build time
via its Java class,
org.apache.openjpa.enhance.PCEnhancer
.
You can also enhance via Ant; see Section 1.2, “ Enhancer Ant Task ”.
Example 5.1. 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 Section 3, “ Command Line Configuration ” ), 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 Section 1, “ Persistent Class List ”). You must, however, supply the classpath you wish the enhancer to run with. This classpath must include, at minimum, the openjpa jar(s), persistence.xml and the target classes.
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.
The Java EE specification includes hooks to automatically enhance JPA entities when they are deployed into a container. Thus, if you are using a Java EE-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 enhancement hooks, consider using the build-time enhancement described above, or the more general runtime enhancement described in the next section.
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 Section 1, “ Persistent Class List ”. 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.
Example 5.2. 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 Section 4, “ Plugin Configuration ”). The agent accepts the long form of any of the standard configuration options (Section 3, “ Command Line Configuration ” ). It also accepts the following options, the first three of which correspond exactly to the same-named options of the enhancer tool described in Section 2.1, “ Enhancing at Build Time ”:
addDefaultConstructor
enforcePropertyRestrictions
scanDevPath
: 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:
true
runtimeRedefinition
: Boolean controlling whether OpenJPA
class redefinition should be available in this JVM execution. Default:
true
Example 5.3. Passing Options to the OpenJPA Agent
java -javaagent:/home/dev/openjpa/lib/openjpa.jar=addDefaultConstructor=false com.xyz.Main
If a javaagent is not provided via the command line and OpenJPA is running on the Oracle 1.6 SDK or IBM 1.6 JDK (SR8+), OpenJPA will attempt to dynamically load the Enhancer that was mentioned in the previous section. This support is provided as an ease of use feature and it is not recommended for use in a production system. Using this method of enhancement has the following caveats:
The dynamic runtime enhancer is plugged into the JVM during creation of the EntityManagerFactory. Any Entity classes that are loaded before the EntityManagerFactory is created will not be enhanced.
The command line javaagent settings are not configurable when using this method of enhancement.
Just as with the Javaagent approach, if you declare a persistent class list, then OpenJPA will only search for metadata and try to enhance the listed classes.
When then dynamic enhancer is loaded, the following informational message is logged:
[java] jpa.enhancement INFO [main] openjpa.Runtime - OpenJPA dynamically loaded the class enhancer. Any classes that were not enhanced at build time will be enhanced when they are loaded by the JVM.
Setting the property openjpa.DynamicEnhancementAgent to false will disable this function.
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 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.
Runtime Unenhanced Classes: AKA 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.
To enable Runtime Unenhanced Classes for a specific persistence unit, add the following property to persistence.xml:
<properties> . . . <property name="openjpa.RuntimeUnenhancedClasses" value="supported"/> . . . <properties>
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.