Wednesday, November 18, 2009
OpenJPA tutorial for beginners available
The tutorial steps through the parts of OpenJPA, including the way it uses annotations, the persistence.xml file and JPQL. To keep it simple and to allow the user maximum flexibility the complete working code is provided.
You can download the tutorial here:
IBM Websphere OpenJPA Tutorial Part 1
*free site registration required
Thursday, October 29, 2009
WebSphere JPA 2.0 Open Alpha is now available!
The WebSphere JPA team is proud to announce the availability of the IBM WebSphere Application Server V7 Java Persistence API (JPA) 2.0 Open Alpha. Details can be found here...
https://www14.software.ibm.com/iwm/web/cc/earlyprograms/websphere/wsasjpaoa/
This JPA 2.0 Open Alpha is based off the Public Final Draft #2 of the JPA 2.0 specification, which can be found here...
http://jcp.org/en/jsr/detail?id=317
If you are looking for something a bit more readable that highlights the new JPA 2.0 features, you could also reference the following two articles...
http://www.ibm.com/developerworks/websphere/techjournal/0909_col_sutter/0909_col_sutter.html
http://www.ibm.com/developerworks/java/library/j-typesafejpa/
Since the WebSphere JPA solution is built on top of the Apache OpenJPA project, you can always follow our progress via these URLs...
http://openjpa.apache.org/
http://openjpa.apache.org/jpa-20-roadmap.html
Enjoy!
Kevin
Thursday, June 25, 2009
OpenJPA with solidDB
http://www.ibm.com/developerworks/websphere/techjournal/0906_vines/0906_vines.html
Enjoy!
Kevin
Thursday, May 21, 2009
Intro to OpenJPA Caching
A part of the JPA 2.0 spec (JSR317) defines the javax.persistence.Cache Interface [1] which exposes a providers' second level(L2) cache. While exploring this new interface, I learned a few things about OpenJPA caching that were not entirely obvious to me at the time. Hopefully I can lay out some of what I've learned to save at least one person some pain.
First off, I'm going to discuss the caching that wasn't obvious to me. Take a quick look through the semi-pseudo code that models the scenario I was running below.
Entity e = em.findEntity(Entity.class, Long.valueOf(1))
updateEntityViaJDBC(e.getId(), "new data")// a worker method to insert data using JDBC
e = em.findEntity(Entity.class, Long.valueOf(1))
if(e.getData().equals("new data")==false){
//Whoops, where did my updated data go!?!
}
As you can see in the example above, I find an Entity from the database and then update the database using JDBC. Since my database was updated I figured I needed search again to update my Entity and finally I validated that the Entity was holding onto the data I *thought* it should. The last part of my logic is where I went astray.
Once an Entity is loaded by OpenJPA, it is characterized as a managed Entity. When an Entity is managed by the JPA runtime, the spec says that "Synchronization to the database does not involve a refresh of any managed entities unless the refresh operation is explicitly invoked on those entities" [2]. The more I dug into this one, I found that OpenJPA tries to cache/optimize where ever the spec allows. If you were to create an EntityManager and then call em.find() on the same object 1000 times in a row, OpenJPA would only hit the DB once. I didn't expect that to happen, but I can swallow it now that I know that it is happening! This caching is sometimes referred to as the EntityManager L1 cache and it is scoped to the life of an EntityManager. In short, when an EntityManger falls out of scope or is closed, the L1 cache is cleared. I'm not going to lie, this stuff is complicated and I only discussed a small part of the entire picture. If you want/need more details, please see [3] for the entire description.
One thing to note is that the previous paragraph talked only about the EntityManager L1 cache which is defined by the spec and it shouldn't be confused with the following paragraph which pertains to the L2 cache.
The javax.persistence.Cache interface that is being introduced as part of JSR317 essentially exposes some of the functionality from org.apache.openjpa.persistence.StoreCache that has been in existence since the early days of OpenJPA. The interface itself is not that interesting, but the results from enabling the OpenJPA data cache are pretty impressive. The OpenJPA user manual states that "This cache is designed to significantly increase performance while remaining in full compliance with the JPA standard. This means that turning on the caching option can transparently increase the performance of your application, with no changes to your code." I hate to say this, but it is a case of where you can get something for free. Enabling the data cache is as simple as adding the following property to your persistence.xml.
< name="openjpa.DataCache" value="true">
For more information regarding the OpenJPA data cache see [4].
-Rick
http://jcp.org/aboutJava/communityprocess/pfd/jsr317/index.html --JSR317 download page.
[1] See 6.10 of JSR317.
[2] See 3.2.4 of JSR317.
[3] Chapter 6 of JSR317.
[4] http://openjpa.apache.org/builds/1.0.2/apache-openjpa-1.0.2/docs/manual/ref_guide_caching.html
Tuesday, April 28, 2009
Custom ORM with OpenJPA
While JPA provides a very rich set of mapping constructs, it doesn't offer much in terms of customization in situations where a domain model cannot be directly or easily mapped to the database using those constructs. This is more typically an issue when an existing application is being converted from some persistence mechanism such as a JDBC-based DAO to JPA and its domain classes cannot suffer more than minimal modifications.
For example, let's say there is an application XYZ which has a domain class named User. The User class contains an email address field which is of type string. Application XYZ expects email addresses in the form user@domain. However, the database table backing the User class stores the components of an email address in separate columns, EMAIL_USER and EMAIL_DOMAIN. The persistence mechanism used in XYZ (for example, JDBC) performs the necessary transforms in a DAO layer to compose the database columns to a single string value of the expected format and vice versa.
Converting this application to JPA would typically involve mapping the individual persistent fields for user and domain to the individual database columns and then wrappering them with the logic formerly provided by the DAO to do the transforms. While this is fairly straight forward, it requires modifications to the domain object and couples the transform logic to the entity. These types of changes can be invasive and undesirable in an existing application.
OpenJPA has an elegant solution for handling these types of mappings: mapping strategies. Custom mapping strategies can be applied to persistent fields using OpenJPA’s @org.apache.openjpa.persistence.jdbc.Strategy annotation. Strategies are most simply created by subclassing the org.apache.openjpa.jdbc.meta.strats.AbstractValueHandler class. Although, for complex mappings you may decide to provide a full implementation class by implementing the org.apache.openjpa.jdbc.meta.ValueHandler interface. To implement a basic strategy you simply need to provide column mapping information and the methods to transform to and from a given type. The name of the custom strategy class is specified on the @Strategy annotation. At runtime, OpenJPA applies this strategy to the persistent field or property. This decouples the mapping and data transformation logic from the domain class. Below is a simple strategy for the multi-column email address mapping. It maps a single persistent field to two database columns and provides the transforms to get the data in the proper format to/from the domain model and database table.
EmailAddressStrategy.java
package strategies;
import org.apache.openjpa.jdbc.kernel.JDBCStore;
import org.apache.openjpa.jdbc.meta.ValueMapping;
import org.apache.openjpa.jdbc.meta.strats.AbstractValueHandler;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ColumnIO;
import org.apache.openjpa.meta.JavaTypes;
public class EmailAddressStrategy extends AbstractValueHandler {
// Create the custom column mappings for this strategy
public Column[] map(ValueMapping vm, String name, ColumnIO io,
boolean adapt) {
Column user = new Column();
user.setName("EMAIL_USER");
user.setJavaType(JavaTypes.STRING);
Column domain = new Column();
domain.setName("EMAIL_DOMAIN");
domain.setJavaType(JavaTypes.STRING);
return new Column[]{ user, domain };
}
// Transform the email address value to its individual datastore column values.
public Object toDataStoreValue(ValueMapping vm, Object val,
JDBCStore store) {
if (val == null)
return null;
// Split the user and domain components into distinct values
if (val instanceof String) {
String strVal = (String)val;
String user = strVal;
String domain = null;
int atIndex = user.indexOf('@');
if (atIndex != -1) {
user = strVal.substring(0, atIndex);
domain = strVal.substring(atIndex+1);
}
return new Object[] { user, domain };
}
return val.toString();
}
// Transform the separate datastore values to an email address.
public Object toObjectValue(ValueMapping vm, Object val) {
if (val == null)
return null;
if (val instanceof Object[]) {
String user = null;
String domain = null;
Object[] objArray = (Object[])val;
if (objArray.length > 0)
user = objArray[0].toString();
if (objArray.length > 1)
domain = objArray[1].toString();
return user + "@" + domain;
}
return val.toString();
}
}
User.java
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.apache.openjpa.persistence.jdbc.Strategy;
@Entity
public class User {
@Id
@GeneratedValue
private int id;
// Use email address strategy on this persistent field
@Strategy("strategies.EmailAddressStrategy")
private String emailAddress;
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getEmailAddress() {
return emailAddress;
}
}
In short, if you find that JPA's mapping constructs are not quite adequate for a particular mapping, creating your own mapping strategy may be just the ticket.
-Jeremy
Sunday, April 19, 2009
What's in a word?
I wanted to understand what issues are important to the growing base of OpenJPA users. So I attempted a small experiment as follows:
- Extracted the headers from the users' postings in OpenJPA Users' Mailing List.
- The extraction process was a breeze with the help of HTML Parser. The program found a list of 1059 independent poster headers.
- Fed the headers to Wordle to create a Word Cloud.
This is the Word Cloud after the first attempt
The original Word Cloud is here
Of course, "problem" is often what makes a user to post in the forum. To focus further and because did not like "problem" being so prominent in the Word Cloud, removed the following words for the input list of headers: "OpenJPA", "entity", "mapping", "error", "using", "Re:"
The original Word Cloud is here
Tuesday, April 14, 2009
OpenJPA Enhancement Eclipse Builder
Please contact me with any questions/comments.
-Rick
For steps documented below, I have the following directory structure. Note, these steps must be followed for each project that has Entities that need to be enhanced.
enhance.xml <- the OpenJPA builder.... download here.
/bin <- Compile directory
/src <- Source directory
/jpa_lib <- OpenJPA binary and all jars from the lib dir of the binary download
commons-collections-3.2.jar
commons-lang-2.1.jar
commons-pool-1.3.jar
derby-10.2.2.0.jar
geronimo-jpa_2.0_spec-1.0-EA-SNAPSHOT.jar
geronimo-jta_1.1_spec-1.1.1.jar
openjpa-2.0.0-SNAPSHOT.jar
serp-1.13.1.jar
/lib <- other libs
- After you add the enhance.xml file to your file system, make sure to refresh your Eclipse workspace so it knows about the newly added file. Make sure that the enhance.xml file is listed in the Navigator view.
- Right click on the Eclipse project that you want to enhance and click on properties.
- Click on the builders filter, and create a new Ant builder.
- Name your builder, then click on "Browse Workspace" in the buildfile box. If you downloaded the enhance.xml file and refreshed your workspace, it should be listed there. If not, go back to step 1 and make sure that Eclipse detects your enhance.xml file.
- In the "Base Directory" box, click on the variables button and select build_project. This should refer to the root of your project. In the directory structure above, it refers to "builder_project".
- In the "Arguments" box you need to add the following properties -Dopenjpa.libs and -Dbuild.dir. -Dopenjpa.libs is the path to the OpenJPA libs, relative to the root of the project. -Dbuild.dir is the path to the build directory, relative to the root of the project. In the directory structure above, openjpa.libs should be set to jpa_lib and build.dir should be set to bin.
- Click on the "Targets" tab along the top.
- You need to set the enhance target to run as a part of "Manual Build" and "Auto Build".
Screenshots:
Files:
[1]http://filebin.ca/zbfbg/enhance.xml -- enhance.xml
[2]http://filebin.ca/aayeg/navigator.png -- Navigator pane
[3]http://filebin.ca/cznvmv/main.png -- Edit builder configuration - main
[4]http://filebin.ca/fqnadv/targets.png -- Edit builder configuration - targets
Thursday, March 19, 2009
Jazz and Software Development
Jazz is said to be the fundamental rhythms of human life and man’s contemporary reassessment of his traditional values. The ancient beats and rhythms of Africa found a contemporary expression in the new world of New Orleans around early twentieth century. Often music historians spoke about undefinable quality of Jazz. Louis Armstrong -- one of the greatest Jazz musician ever - put is succinctly : "If you gotta ask, you’ll never know". The unscripted collaboration where every man plays his own tune but responds and respects others' -- is the core theme that gave rise to this uniquely American contribution to music.
It is intriguing that the new software collaboration platform built on Eclipse from contributions by pioneers such as Erich Gamma is named Jazz. Naming is very important. Naming captures aspiration. A software engineer as great as Erich Gamma can foresee the evolution of software development practices or at least where he wants it to go. Building software is playing Jazz -- not Orchestra.
Sunday, February 22, 2009
Dynamic Fetch Planning
Now I have heard this before:
"Hibernate can do X, can your thing do X?"
I mumble in a me-too manner. Because Hibernate, as the world knows, is 42 -- the answer to the eternal question. Also I have seen many postings in OpenJPA's own mailing list that can be paraphrased as: "We are migrating our Hibernate application to OpenJPA, but we could do X with Hibernate, OpenJPA does not seem to be able to do X". Or a slight variation on the theme: "This test fails with OpenJPA. But it passes with Hibernate".
As a contributor to OpenJPA, this implied second-class status irks me at times (I also wonder: why are they migrating away from Hibernate anyway?). So when the following email from an unknown sender reached my mailbox -- I smiled. The mail says:
"We are working in investment banking's IT sector. We have a requirement X that Hibernate can not support. Can OpenJPA do X?"
Firstly, I was happy to note that OpenJPA can do X very well.
Secondly, the proposal to include feature X to JPA 2.0 Specification was simply ignored.
Thirdly, investment banking's IT is the bellwether of enterprise technology trends and demands.
I must admit that I do not know Hibernate deep enough to ascertain that it can or can not do X -- I am just going by the sender's comments.
So what is this feature X? It is called Dynamic FetchPlan.
The conventional wisdom is to mix fetch plan with query. This wisdom is rooted in the history of SQL -- where projection and selection criteria appear together in an all-encompassing SQL statement (I have heard stories of a single SQL statement being 2000 lines long). JPA took the same route -- its query language JPQL introduced FETCH JOIN which lets you specify what instances (that are not directly part of the projected result) will be brought in the memory as a side-effect of query execution.
While OpenJPA supports fetch join as per JPA specification -- OpenJPA prefers to separate these two concerns, namely: i) what is selected and ii) what is fetched. Because fetch plan is independently specified, you can issue a simple EntityManager.find(Company.class, "acme.org") -- and as a result the persistent context may either be populated with a single Company instance or with all the Departments and all the Employees of those Departments with their Addresses and Spouses' names. It all depends on what fetch plan is active while you invoked EntityManager.find(). In one end of the spectrum, you can use basic default fetch plan which fetches the fields of primitive types but no relations. On the other end, you can be as creative as you wish to specify a sub-graph of the complete closure starting from a root Company instance. And you can specify these fetch plans during design, use them and edit them at runtime.
But why should you care to carve out a sub-graph from a root entity? Why does OpenJPA use fetch plan as a pervasive notion throughout its internal design? Why even the question may be worth pondering?
My take on it will take a separate post -- more importantly Oscar is starting in TV....
Thursday, February 12, 2009
OpenJPA Enhancement
I have cruised around the OpenJPA forums enough to notice that numerous new developers have posted questions regarding enhancement. Since I'm new to OpenJPA myself, I figured I'd dive in to enhancement and see what all the fuss is about. This post is directed mainly at developers that are taking a first look at OpenJPA and having problems getting started. No worries for those of you deploying an application to run in a Java EE 5 compliant application server such as WebSphere Application Server because your Entities will be automatically enhanced at runtime.
As I was exploring how to enhance, I started to wonder why do I need to enhance at all? As it turns out the JPA spec requires some type of monitoring of Entity objects, but the spec does not define how to implement this monitoring. Some JPA providers auto-generate new subclasses or proxy objects that front the user's Entity objects, while others like OpenJPA use byte-code weaving technologies to enhance the actual Entity class objects. Now that we've got that out of the way, lets get back to how to do the enhancement. I'm going to give runtime examples first, as I feel those are the easiest from a developers point of view.
Runtime enhancement-When running in a JSE environment or in a non EE-5 compliant container, OpenJPA defaults to using subclassing enhancement. The subclassing enhancement support was added originally as a convenience to new developers to reduce the amount of work to get a 'HelloWorld-ish' OpenJPA application working out of the box. It was never meant to run in production. So you're probably thinking that this sounds great! OpenJPA handles enhancement automatically for me and I can stop reading this post. Wrong! Subclassing has two major drawbacks. First off, it isn't nearly as fast as byte-code enhancement and the second drawback is that there are some documented functional problems when using the subclassing support. The moral of the story is, don't use this method of enhancement. I'm not the only one making this recommendation as I've come across form posts about removing subclassing enhancement as the default OpenJPA behavior. Additional information regarding the subclassing enhancement can be found here.
The second and recommended way get runtime enhancement is to provide a javaagent when launching the JVM that OpenJPA is going run in. This is the method that I use in most of my test environments because it is very painless. All that is required to get runtime enhancement in Eclipse is to specify the -javaagent:[open_jpa_jar_location] on the Run Configuration page. That is it.
Another simple way to get runtime enhancement is to specify the the -javaagent when launching an application from ant. Below is a snippet from my build.xml that shows how to pass the -javaagent when launching a JSE application that uses OpenJPA.
<path id="jpa.enhancement.classpath">Buildtime enhancement -
<pathelement location="bin"/>
<!-- lib contains all of the jars that came with the OpenJPA binary download -->
<fileset dir="lib">
<include name="**/*.jar"/>
</fileset>
</path>
...
<target name="drive" depends="clean, build">
<echo message="Running test with run time enhancement."/>
<java classname="main.Driver" failonerror="true" fork="yes">
<jvmarg value="-javaagent:${openJPA-jar}"/>
<classpath refid="jpa.enhancement.classpath"/>
</java>
</target>
In this section I'm going to cover how to invoke the build time enhancer ant task that is packaged with OpenJPA through the use of ant and maven. Yes there are many different ways to buildtime enhancement bliss, but I'm only going to talk about using the ant task. Note that this entire section assumes you're running the ant/maven scripts from a command line. I can't say for certian how this works inside Eclipse. Warning: I'm not a maven wizard, so please be nice if I've commited some cardinal maven sin. :-)
I'll start first by showing how to define a OpenJPA enhancer task and how to invoke the task in ant. I had to fumble a little to get it working, but overall it was pretty straight forward. First you'll need to compile the Entites. (Note: as a prereq to running the enhance task, I copied my persistence.xml file to my /build directory. You might not need to do this, but the persistence.xml has to be in the classpath.) Next you'll need to configure the enhancer task and a classpath where the task can be found.(Adding the classpath is the part that the OpenJPA manual missed.) The final step is to call the enhance task. A snippet is provided below.
<path id="jpa.enhancement.classpath">The second(different) path to build time enhancement is to use the maven antrun plug-in to launch the OpenJPA enhancer task. Since I'm new to maven this road was pretty clunky, but the steps were nearly identical to running directly in ant. I'll include the parts from my pom.xml that took the most time to figure out. Again, I'm not sure if you will need to move the persistence.xml file to the build directory, but I did(I assume I'm not doing something right).
<pathelement location="bin"/>
<!-- lib contains all of the jars that came with the OpenJPA binary download -->
<fileset dir="lib">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="enhance" depends="build">
<!-- This is a bit of a hack, but I needed to copy the persistence.xml file from my src dir
to the build dir when we run enhancement -->
<copy includeemptydirs="false" todir="bin">
<fileset dir="src" excludes="**/*.launch, **/*.java"/>
</copy>
<!-- define the openjpac task -->
<taskdef name="openjpac" classname="org.apache.openjpa.ant.PCEnhancerTask">
<classpath refid="jpa.enhancement.classpath"/>
</taskdef>
<!-- invoke enhancer the enhancer -->
<openjpac>
<classpath refid="jpa.enhancement.classpath"/>
</openjpac>
<echo message="Enhancing complete."/>
</target>
<build>Hopefully this helps someone out there! Feel free to contact me if you have any questions/comments/suggestions.
<!-- Copy the persistence.xml file to the build dir -->
<!-- You can skip this step if you put the persistence.xml in src/main/resources/META-INF instead of src/main/java/META-INF -->
<resources>
<resource>
<directory> src/main/java </directory>
<includes>
<include> **/*.xml </include>
</includes>
</resource>
</resources>
<plugins>
.....
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.2</version>
<executions>
<execution>
<phase>process-classes</phase>
<configuration>
<tasks>
<taskdef name="openjpac" classname="org.apache.openjpa.ant.PCEnhancerTask" classpathref="maven.compile.classpath"/>
<openjpac>
<classpath refid="maven.compile.classpath"/>
</openjpac>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
....
</build>
-Rick
Tuesday, February 3, 2009
The problem with JPA and Java persistence in general...
I just came across another interesting article from a colleague of mine (Billy Newport). Billy has a very active blog on lots of different topics related to Java Development (and a few items not related to programming in the least). But, this one caught my eye due to the title, "The problem with JPA and Java persistence in general..." Surprised by my attention? :-)
I'm not going to try to steal Billy's thunder. If you are interested in debating Billy's viewpoints, you are more than welcome to do so via his blog. I just thought that the content may be of interest to the readers of this blog, so I thought I would cross-post it.
But... I would like to comment that I think the Java EE community has made some great strides in the area of persistence. Not all of the attempts have been welcomed with open arms (ie. CMP Beans), but we really seem to be getting acceptance with the JPA approach. There are several fully-functional implementations of the JPA 1.0 specification, and the JPA 2.0 expert group is extremely active finalizing the next revision of the spec.
So, let me just ask for general comments or suggestions for a Java persistence solution. Is JPA satisfying your persistence needs? What about WebSphere's JPA solution based on OpenJPA? If you are not using it, why not? And, what are you using instead? CMP beans? Straight JDBC? Hibernate? Something else?
Thanks,
Kevin
Monday, February 2, 2009
OpenJPA and pureQuery
In WebSphere Application Server V7, we deliver an enhanced Java Persistence API (JPA) implementation that supports static SQL access to DB2.. This may not mean much to you, but your DB2 DBAs will certainly understand the implications of this. In general, Java database access, whether through JDBC or OpenJPA is all dynamic SQL. From a security and performance perspective, static SQL can be better. For some applications, it can be significantly better.
For those of you who have never heard of static SQL, Figure 1 says it all - static SQL is basically preprocessed so all the database access processing does not have to happen when real users are using the application. As well as reducing processing time, it also means that performance stays more consistent. There is no up and down of performance based on whether the query is in the SQL statement cache or not.
To take advantage of this feature, you don't need to change anything about your JPA application; however you do need to have the pureQuery Runtime. WebSphere AS provides the utility that generates the SQL from the persistence unit along and for any JPA named queries. For more on how to actually do this, see this article in the WebSphere Tech journal.
You can see some performance results of using static SQL with pureQuery in this article. More background material on Static SQL can also be found in this article.
To find out more about how pureQuery can work together with WebSphere to reduce costs and improve quality of service, be sure to tune in to this webcast, presented by my colleague in pureQuery-land, Steve Brodsky. You can also read his blog entry on the pureQuery and OpenJPA integration.
Enjoy,
Kevin
Thursday, January 22, 2009
JPA Connection Pooling
Modifying an existing application to use connection pooling couldn't be easier. To start, you're going to need to download the Apache Commons Pool and DBCP components and add them to your application classpath. Next you will need to update the application database connection properties. Below is an example openJPA configuration using DBCP. You will need to update the connection info where appropriate if using a different DB. Note that these properties are being set in the META-INF/persistence.xml file, but you can also set the JVM system properties directly.
<property name="openjpa.ConnectionDriverName" value="org.apache.commons.dbcp.BasicDataSource"/>Configuring openJPA to use connection pooling is as simple as that. I created a small JSE application for this post that uses both pooled and non-pooled connections to do 1000 selects on an empty table. Even though the example is very simple, the results are pretty impressive.
<property name="openjpa.ConnectionProperties" value="DriverClassName=com.mysql.jdbc.Driver,Url=jdbc:mysql://localhost/test,Username=root,Password=password"/>
- 1000 selects with no connection pooling took ~23.469 seconds.
- 1000 selects with connection pooling took ~1.234 seconds.
Tuesday, January 13, 2009
Auditing with OpenJPA
A transactional application often requires to audit the persistent entities. In this brief example, I will present a small code example to outline how an application can audit changes via the life cycle callback methods. The JPA specification prescribes how an entity or a separate, stateless entity listener instance can receive callback notification during various life cycle state transition of an entity. Seven transition events are defined: PrePersist, PostPersist, PreRemove, PostRemove, PreUpdate, PostUpdate and PostLoad. To receive these callbacks either annotate certain methods a) of the entity class itself or b) of a separate entity listener class. The signature of the method in the entity class itself should be
void <METHOD>()
Whereas, callback methods defined on an entity listener class have the following signaturevoid <METHOD>(Object pc)
In the later case, the managed entity is passed as an argument.Given this basic framework, how can we implement audit facility such that an application can track any update made on a persistent entity? Of course, to compute this change, the application must be able to access the entity state before and after the update. The central question is:
The answer lies in the fact that OpenJPA can store the state of an entity as it enters the managed lifecycle. This internal copy is used by OpenJPA when a transaction is rolled back. The state of the entities participating in the failed transaction is restored to the original state i.e. as it were before the transaction started. The simple example presented here shows how to access that copy. Of course, a real audit facility has to figure out how to compute the difference between the current and previous state of an entity and where to record those differences.
Let us consider a simple persistent instance, audit.PObject
.1: package audit;
2:
3: import javax.persistence.Entity;
4: import javax.persistence.Id;
5: import javax.persistence.PostUpdate;
6:
7: import org.apache.openjpa.enhance.PersistenceCapable;
8: import org.apache.openjpa.kernel.SaveFieldManager;
9: import org.apache.openjpa.kernel.StateManagerImpl;
10:
11: @Entity
12: public class PObject {
13: @Id
14: private long id;
15:
16: private String name;
17:
18: public long getId() {
19: return id;
20: }
21:
22: public void setId(long id) {
23: this.id = id;
24: }
25:
26: public String getName() {
27: return name;
28: }
29:
30: public void setName(String name) {
31: this.name = name;
32: }
33:
34: @PostUpdate
35: public void audit() {
36: PersistenceCapable currentState = (PersistenceCapable)this;
37: StateManagerImpl sm = (StateManagerImpl)currentState.pcGetStateManager();
38: SaveFieldManager sfm = sm.getSaveFieldManager();
39: PersistenceCapable oldState = sfm.getState();
40: PObject old = (PObject)oldState;
41:
42: System.err.println("old : " + old);
43: System.err.println("current : " + this);
44: }
45:
46: public String toString() {
47: return this.getClass().getName()+"@"
48: + Integer.toHexString(System.identityHashCode(this))
49: + "[" + name +"]";
50: }
51: }
Line 36: Cast this instance to org.apache.openjpa.enhance.PersistenceCapable interface. The cast is safe. Because OpenJPA ensures that every persistent class implements PersistenceCapable. OpenJPA actually modifies original bytecode of audit.PObject class. This bytecode modification process is called enhancement and is described in detail in OpenJPA documentation.
Line 37: Get the StateManager. Every PersistenceCapable instance is managed by a StateManager. It is the StateManager who intercepts every access and mutation of entity state and calls the requisite underlying OpenJPA infrastructure to load or store the state of an entity which in turn may access the database via JDBC.Line 38: Get the SaveFieldManager. A StateManager may refer to a SaveFieldManager to which StateManager delegates the task of maintaining a copy of managed instance.
Line 39: Get the old state. The old state is maintained by the SaveFieldManager.Line 40: Cast is back to audit.PObject. This entity refers to the state of PObject as it entered a transaction.
At this point, a real audit application perhaps will do some sort of state comparison to determine what has changed between current and the previous state of this instance. I am just printing their values on the console.Now let us write a simple "Hellow JPA World" style application to check that the callback is received. I am omitting the scaffolding code to get a JPA EntityManagerFactory etcetera and just listing the transactional method.
1: public void testPostPersistCallabck() {
2: EntityManager em = emf.createEntityManager();
3: em.getTransaction().begin();
4: PObject pc = new PObject();
5: pc.setId(1001);
6: pc.setName("X");
7: em.persist(pc);
8: em.getTransaction().commit();
9: Object pid = pc.getId();
10: em.clear();
11:
12: em = emf.createEntityManager();
13: em.getTransaction().begin();
14: PObject audit = em.find(PObject.class, pid);
15: audit.setName("Y");
16: em.getTransaction().commit();
17: }
203 test INFO [main] openjpa.jdbc.JDBC - Using dictionary class "org.apache.openjpa.jdbc.sql.MySQLDictionary".As the last two lines of log output shows, the audit() callback has been invoked and the method had printed the state of the instance as it were before its name is changed from "X" to "Y".
1078 test TRACE [main] openjpa.jdbc.SQL - <t 21933769, conn 11875256> executing prepstmnt 7245716 INSERT INTO PObject (id, name) VALUES (?, ?) [params=(long) 1001, (String) X]
1156 test TRACE [main] openjpa.jdbc.SQL - <t 21933769, conn 23978087> executing prepstmnt 1440568 SELECT t0.name FROM PObject t0 WHERE t0.id = ? [params=(long) 1001]
1172 test TRACE [main] openjpa.jdbc.SQL - <t 21933769, conn 14837200> executing prepstmnt 30702379 UPDATE PObject SET name = ? WHERE id = ? [params=(String) Y, (long) 1001]
old : audit.PObject@1581e80[X]
current : audit.PObject@3a9d95[Y]
The two critical points before we close.
This technique only works with build-time enhancement.
OpenJPA must be configured to record the state of persistent entities participating in a transaction. This is specified by the following property:
<property name="openjpa.RestoreState" value="all"/>