JPA persistence in OSGi with openJPA


Applying a persistence framework in an OSGi container is not always a nuisance. Some persistence frameworks claim to be OSGi ready, but nevertheless, deploying them in a real OSGi application can be quite hard. In this blog, we’ll try to get openJPA to work in Apache Felix. All source code used for this experiment can be found in this zip.

For our experiments, we take a simple JPA example, consisting of one entity class Person and a Persister class that will either list all the person items in the database, or add a new one. For example, its create method looks like this:

public void create(Person person) {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();
    try {
        transaction.begin();
        entityManager.persist(person);
        transaction.commit();
    }
    finally {
        if (transaction.isActive())
            transaction.rollback();
        entityManager.close();
    }
}

Both operations (create & list) are contained in an interface that is published as an OSGi service, which enables code external to our jpa bundle to use this functions.

The first step in our little experiment is of course to create a bundle for these classes. We’ll use the Bnd tool for this, wrapped in the ant task that is available on http://opensource.luminis.net. We’ll start with the naive approach: we create a bundle that only contains our sample classes, add an activator and see what happens. The activator calls the Persister.initJPA() method in its start method, that tries to create the EntityManagerFactory:

private void initJPA() {
    Properties props = new Properties();
    entityManagerFactory = Persistence.createEntityManagerFactory("openjpa", props);
    if (entityManagerFactory == null)
        throw new RuntimeException("Creating EntityManagerFactory failed");
    else
        System.out.println("Init JPA ok.");
}

Of course JPA can not function without its persistence.xml file, which we add in the META-INF directory of our bundle (see build-attempt1.xml).

Installing this bundle in Felix results in one unresolved package: javax.persistence. This is easy to understand: our Person class uses JPA annotations (@Entity, @Id). We'll take the geronimo-jpa_3.0_spec-1.0.jar that comes with openJPA and add this to our bundle (eventually, we would like to make this separate bundles, but let's focus on getting it to work first). To do so, we adapt the build file to include the jar as a resource and set it on the bundle classpath explicitely (see build-attempt2.xml).

Loading this next version of the bundle in Felix, results in an exception: because the EntityManagerFactory is null. Note that there is no openJPA exception that gives us a clue about what is going wrong, but it's likely that is has something to do with the fact that we did not yet deploy any openJPA jars. So we add the openjpa-1.0.2.jar to the bundle and try again. If we try to load this bundle in Felix, it complains about an unresolved org.apache.tools.ant.types package. If we study the Import-Packages header generated by Bnd, we see a long list of missing dependencies, containing the usual suspects like org.apache.commons.lang, but also containing items like

  • com.ibm.websphere.jtaextensions
  • oracle.sql
  • org.apache.tools.ant.taskdefs
  • sun.misc
  • weblogic.transaction

which will a bit harder to track down. We take the pragmatic approach of adding the packages that (we think) openJPA really needs and ignore the others. In the end, we won't get away with this approach, but for now, it suffices. See the build file build-attempt5.xml for the exact list of ignored packages and the openJPA libaries that are added on the bundle classpath.

Note that the geronimo-jta_1.1_spec-1.1.jar that provides javax.transaction is needed, even though this package is delivered with the JDK and Felix makes it available on the system classpath. The funny thing is, is that the JDK version of this packages includes only a few classes of this package (and its javax.transaction.xa subpackage). This leads to the strange behaviour that the bundle does not load with a ClassNotFoundException, even though the package javax.transaction can be resolved!

Unfortunately, adding all these jars did not resolve the issue of having no EntityManagerFactory. This turns out to be a classloader issue: the openJPA classes are on the bundle classpath, but the JPA bootstrap code (the Persistence class) doesn't know about the bundle classloader. This can be solved by passing the bundle classloader as context classloader:

private void initJPA() {

    ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
    Properties props = new Properties();
    entityManagerFactory = Persistence.createEntityManagerFactory("openjpa", props);
    ...
    Thread.currentThread().setContextClassLoader(oldCL);
}

Note the last line of this method: its considered good practice to switch back to the original context classloader before returning.

Now the initJPA method seems to succeed, it's time for a real test. If you downloaded the source code of this samples, you'll find a seperate bundle (perscmd.jar) that adds an interactive command to the Felix shell, that will talk to our PersonPersistence service. This command bundle works only with Felix (i.e. with the Felix command shell), but you can easily create a similar bundle for other frameworks, e.g. Equinox or Knopflerfish.
Running this command shows that we're not done yet: the jdbc driver class can't be loaded. Adding the jdbc driver jar to the bundle classpath (postgres in our case) solves this last classloader problem and after creating a database and a "person" table, we can finally create persistent Person classes, as the following trace of the Felix interactive shell shows:

-> persistence list
List of all persons in db:
-> persistence create 1 john
-> persistence create 2 mike
-> persistence list
List of all persons in db:
john (1); registered on Mon Mar 24 21:19:53 CET 2008
mike (2); registered on Mon Mar 24 21:20:00 CET 2008
->

The conclusion is that it is fairly simple to use openJPA in an OSGi bundle. Actually, i was quite surprised how easy it turned out to be, given my previous experience with other persistency frameworks. I guess the fact that a library is so easily deployed in a context it was not designed for, tells us something about the quality of the library. And by the way, i got the same impression about quality when browsing through the code, trying to pinpoint the few problems i had to solve.

Conclusion

There are a few things to improve. First, bundling everything in the same jar is a bit of a brute force approach; ideally, we should create one or more separate bundles for the openJPA stuff. I did some work on this already, maybe i'll come back to that later.
Second, we created a sort of a work around for the unresolved imports, which should be solved differently.
However, i think both issues are doable in reasonable time, so for me openJPA is considered a good candidate for adding persistence to OSGi applications.

, ,

  1. #1 door Yaytay om 15 mei 2010

    This all became a bit simpler with OpenJPA 2.0.
    I’ve just written a blog on using OpenJPA 2.0 in Felix, where the OpenJPA is a bundle in its own right.
    http://yaytay.wordpress.com/2010/05/13/lsned-33-openjpa-in-apache-felix/

(wordt niet gepubliceerd)
  1. Nog geen trackbacks.