My blog has moved!

You should automatically be redirected in 6 seconds. If not, visit
http://blogs.i2m.dk/allan
and update your bookmarks.

Friday, 24 October 2008

Replacing TopLink Essentials with OpenJPA as my persistence provider

During the development of my latest pet project I decided to go head-on with many of the latest Java Enterprise APIs. One of these was the Java Persistence API (JPA), which I had already used in a handful of projects before. On previous projects the persistence requirements were very simple. I could use JPA out-of-the-box with TopLink Essentials which is the standard set-up for a JPA project in NetBeans/GlassFish. However, for this new pet project of mine I was in need of storing large binary objects (BLOBs). I was shocked to discover that TopLink Essentials doesn't support the Fetching configuration for relationships and properties. Instead it will Fetch.EAGER everything in a relationship and property. This made my application crash hard (OutOfMemoryException) when ever I would query for all entities containing the BLOB. So, I set out to replace the persistence provider. First I looked at Hibernate. I used Hibernate before JPA was released and never had much trouble with it. Unfortunately I found that Hibernate also doesn't support the fetching configuration (in JPA mode). That lead me to OpenJPA which really surprised me. It is well documented, clean, easy to use, and support the fetch configuration. I've now replaced the persistence provider on two projects with OpenJPA and the performance has increased significantly. However, here are a few gotchas that you have to look out for:


  • Auto-generated identity fields must not have a preset value in your JavaBean (hence, this would give you problems:

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id = 0L


    Instead you should write

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;


  • Collections are Fetch.LAZY by default, so if you got an existing using TopLink Essentials, you have to double check that your relations are not throwing LazyInitializationException upon fetching outside the transation.

  • Remember to specify the Fetch depth (openjpa.MaxFetchDepth) in persistence.xml for using the Fetch.EAGER configuration

  • TopLink Essentials compiles named queries when your application is deploy on the application server, OpenJPA on the other hand compiles the named queries upon first usage.

  • When enabling SQL DDL on OpenJPA it doesn't generate foreign key constraints, unlike TopLink Essentials



That's all for now. I'd love to hear about your experiences with OpenJPA or any other persistence provide you find suitable for your need.

14 comments:

Shaun Smith said...

Hi Allen,

I'd encourage you to look at EclipseLink (http://www.eclipse.or/eclipselink) which does support lazy loading of Blobs. EclipseLink is based on the full source of Oracle TopLink and is the JPA 2.0 reference implementation. And we have a migration utility for moving from TopLink Essentials which should make it pretty painless.

--Shaun

Allan Lykke Christensen said...

Thanks Shaun! Could you highlight some of the difference/advantages of EclipseLink compared to OpenJPA. Would there be benefit for me to replace OpenJPA?

Shaun Smith said...

Performance and features are the biggest differentiators. EclipseLink has a dedicated performance team that work on improving the code to avoid unnecessary work and on identifying any performance regressions introduced inadvertently by new features. In our internal benchmarking we are significantly faster than OpenJPA. What we have found is that we particularly shine when you scale up into a multi-threaded transaction heavy environment. In a single threaded single client type of app the performance difference may not so obvious.

In terms of features we have many advanced features thanks to 12 years of commercial usage before being open sourced. Our caching infrastructure is one area in which we have a large number of options you can configure to meet your specific needs on a class by class basis. You can find a list of our advanced features here: http://wiki.eclipse.org/Using_EclipseLink_JPA_Extensions_(ELUG)

We're also currently adding support for JPA 2.0 features because EclipseLink is the reference implementation and we need to have everything built before the spec can be finalized. ;-)

--Shaun

Allan Lykke Christensen said...

Thanks for the explanation. I'll try out EclipseLink for an upcoming project and post my comments on the blog.

Paul said...

Shaun: I looked at the EclipseLink but am uncertain whether it can function as an Eclipse plugin. If it can function as such do you have some instructions on how to install it into Eclipse and what sorts of perspectives and views that should be available within that context.

Shaun Smith said...

Paul,

EclipseLink is a runtime framework just like OpenJPA. However besides the regular distribution we also make EclipseLink available for download as a set of OSGi bundles. You can install these in Eclipse 3.4 (Ganymede) by dropping them into the "dropins" folder under the "eclipse" folder.

Once you've done this they are available for use in OSGi projects. There are no UI components, this is pure runtime. Are you building OSGi applications?

If you're looking for JPA tooling then you want Dali which offers Eclipse tools for JPA and in the upcoming Dali 2.1 they'll have very good out of the box support for EclipseLink. Dali is part of WTP (Web Tools Platform). You can add WTP to your existing Eclipse install but the easiest thing to do is just download the "Eclipse IDE for Java EE Developers" distribution from eclipse.org. It includes WTP and Dali.

--Shaun

Paul said...

Shaun:

Thanks for the information. Now I understand. I *am* using Dali right now but I was looking to see if EclipseLink contained its own UI components (which I see it does not). Do you have any idea when Dali 2.1 will hit the scene?

Thanks!

Shaun Smith said...

Dali 2.1 is scheduled to be released December 19th, 2008. We're currently in release candidate mode with a few bugs still to finish off.
See http://wiki.eclipse.org/Dali_2.1Planning

BTW, if you select the EclipseLink platform when you create your JPA project or switch it in the project properties, you'll get enhanced rich ui in the persistence.xml editor for configuring EclipseLink properties. You'll also get support in the Structure and Details views for various advanced mappings provided by EclipseLink.

--Shaun

SocialBiz said...

Shaun:
I just moved from toplink-essentials to eclipselink because of a similar issue as described in this post with exceptions in weaving.

Unfortunately also under the eclipselink weaving did not work with the same exceptions as for toplink-essentials. I have followed the instructions in the eclipselink documentation for dynamic weaving, which is (replaced <> with [] ...):
- in persistence.xml:
[provider]org.eclipse.persistence.jpa.PersistenceProvider[/provider]
[property name="eclipselink.weaving" value="true"/]
- setting the elipselink.jar to jvm
[jvm-options]-javaagent:${com.sun.aas.instanceRoot}/lib/ext/eclipselink.jar[/jvm-options]

. I try to lazy-load a owned 1:1 relationship in NUser entity. Any ideas or is this also a bug same as in toplink-essentials?

thxs Ben

Exception Description: The method [_persistence_setnUserStatus_vh] or [_persistence_getnUserStatus_vh] is not defined in the object [my.NUser].
Internal Exception: java.lang.NoSuchMethodException: my.NUser._persistence_getnUserStatus_vh()
Mapping: org.eclipse.persistence.mappings.OneToOneMapping[nUserStatus]
Descriptor: RelationalDescriptor(my.NUser --] [DatabaseTable(n_user)])
at org.eclipse.persistence.exceptions.DescriptorException.noSuchMethodWhileInitializingAttributesInMethodAccessor(DescriptorException.java:1139)
at org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor.initializeAttributes(MethodAttributeAccessor.java:151)

Shaun Smith said...

Something is wrong in your configuration. The NoSuchMethodException is for a method that weaving adds. By explicitly saying you have weaving enable, EclipseLink is trying to use a woven method, but it isn't there. If you're running in GlassFish and you're injecting your EntityManager then you shouldn't have to specify anything and weaving will happen automatically unless you explicitly turn it off. If you're running in Java SE then you need to boot the VM with the -javaagent option. You don't specify it in the persistence.xml.

BTW, typically the weaving property in persistence.xml is only used to turn it off. EclipseLink will automatically use weaving if running in a container or you start the VM in Java SE with the -javaagent. The persistence.xml property is there when you don't want to use weaving regardless of whether it's enabled. For example, in a situation where the same Entities are included in multiple persistence units.

--Shaun

SocialBiz said...

I am not injecting, but using something like this ( this is actually how it is geneated by NB for restful web services).
Maybe i inject the entity manager instead?

private static String DEFAULT_PU = "MyPU";

private static ThreadLocal[PersistenceService] instance = new ThreadLocal[PersistenceService]() {
protected PersistenceService initialValue() {
return new PersistenceService(DEFAULT_PU);
}
};

private EntityManagerFactory emf;
private EntityManager em;

private PersistenceService(String puName) {
try {
this.emf = Persistence.createEntityManagerFactory(puName);
this.em = emf.createEntityManager();
} catch (RuntimeException ex) {
if (emf != null) {
emf.close();
}

throw ex;
}
}

public static PersistenceService getInstance() {
return instance.get();
}

Shaun Smith said...

Can you clarify your environment details? I think you're running inside GlassFish? If you're in GlassFish then the only way to get weaving enabled is if you let the container create the EntityManagerFactory for you. If you've deployed your persistence unit in an EAR with your web service then you can either inject the EntityManager or obtain it from JNDI.

Take a look at Sahoo's blog for some info on this. Be aware that the first code sample is the code that doesn't work. ;-) http://weblogs.java.net/blog/ss141213/archive/2005/12/dont_use_persis_1.html

--Shaun

SocialBiz said...

Thanks. Yes I am using Glassfish v2ur2 and run restful ws using jersey0.8.

I followed the reference in the blogpost by letting the container create the EntityManagerFactory, and now eclipselink does the weaving as expected.

Unfortunately I encountered now another problem more related to JTA/Glassfish i guess: The UserTransaction object gets not injected..

I have this persistenceSevice class which is a ThreadLocal singleton. not sure if this combination works with injection now:

@PersistenceContext(name = "persistence/MyPU", unitName = "MyPU")
public class PersistenceService {

@Resource
private UserTransaction utx;
private static ThreadLocal[PersistenceService] instance = new ThreadLocal[PersistenceService]() {

protected PersistenceService initialValue() {
return new PersistenceService("persistence/MyPU");
}
};
private EntityManager em;

private PersistenceService(String puName) {
try {
Context envCtx = InitialContext.doLookup("java:comp/env");
em = (EntityManager) envCtx.lookup(puName);
if ( utx == null ){
utx = (UserTransaction) envCtx.lookup("java:comp/UserTransaction");
}
//this.emf = Persistence.createEntityManagerFactory(puName);
//this.em = emf.createEntityManager();
} catch (Exception ex) {
if (em != null) {
em.close();
}
throw new RuntimeException(ex.toString());
}
}

//...

public static PersistenceService getInstance() {
return instance.get();
}

public void rollbackTx() {
try {
//NullPointerException here, because utx is null...
if (utx.getStatus() == Status.STATUS_ACTIVE) {
utx.rollback();
}
} catch (javax.transaction.SystemException ex) {
throw new JTAResourceException(ex.toString());
}
}

public void close() {
if (em != null && em.isOpen()) {
rollbackTx();
em.close();
}
removeInstance();
}

PaJaSoft said...

Ad generating Foreign Keys see http://openjpa.apache.org/faq.html (Why OpenJPA is not creating foreign key constraints on the database tables?)