2.  Enhancement

2.1. Enhancing at Build Time
2.2. Enhancing JPA Entities on Deployment
2.3. Enhancing at Runtime
2.4. Enhancing Dynamically at Runtime
2.5. Omitting the OpenJPA 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.

2.1.  Enhancing at Build Time

The enhancer can be invoked at build time via its Java class, org.apache.openjpa.enhance.PCEnhancer.

Note

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.

2.2.  Enhancing JPA Entities on Deployment

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.

2.3.  Enhancing at Runtime

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

2.4.  Enhancing Dynamically at Runtime

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.

2.5.  Omitting the OpenJPA enhancer

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.

    Note

    Runtime Unenhanced Classes has some known limitations which are discussed below and documented in JIRA issue tracker on the OpenJPA website. As a result this option is disabled by default. Support for this method of automatic enhancement may be enabled via the Section 5.65, “openjpa.RuntimeUnenhancedClasses” option.

    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.