EclipseLink is the default JPA provider on the GlassFish JEE server. As such, I figured EclipseLink would work well as the persistence provider. However, we began a recent JEE 6 project using GlassFish v3 and chose Hibernate as the JPA provider because of the team’s familiarity with Hibernate 3. We later switched to EclipseLink to be compatible with another application running on the server — and immediately encountered two annoying problems that never occurred with Hibernate. This article is meant to document those EclipseLink problems in case it helps others with similar EclipseLink issues on GlassFish.
The first problem we saw was a sporadic ClassCastException after a module redeploy. The second problem we found was that entity methods marked with
@PrePersist were not being called when the entity was being saved to the database. Instead, those fields would remain null when EclipseLink executed the
INSERT SQL statement, resulting in constraint violations for our non-nullable columns.
The ClassCastException occurs at a line in our code that assigns an entity to a reference variable declared as the type of its parent class, which is a JPA mapped superclass. A widening cast like that should cause no problem, so this was a head-scratcher. Adding to the mystery is we had no problem with this code when using Hibernate JPA. The entity class in question,
ActionTypeLookup, as shown in the stack trace below, directly extends the parent class type that it is being assigned to.
Here are the partial class definitions. The parent class type,
is a @MappedSuperclass that the entity
ActionTypeLookup directly extends:
The persistence code is defined in a module (an EAR file) containing the JPA persistence unit definition. The ClassCastException occurs only if that same module is unloaded and reloaded from the server several times. That is, something you do a lot of during development. We had loaded and unloaded this EAR file many times on GlassFish with no problem when using Hibernate. Shortly after the switch to EclipseLink, we would see this stack trace in the log at deploy time:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 javax.enterprise.system.core.com.sun.enterprise.v3.server Exception while loading the app javax.ejb.EJBException: javax.ejb.CreateException: Initialization failed for Singleton LookupSessionFacade at com.sun.ejb.containers.AbstractSingletonContainer$SingletonContextFactory.create(AbstractSingletonContainer.java:695) at com.sun.ejb.containers.AbstractSingletonContainer.instantiateSingletonInstance(AbstractSingletonContainer.java:444) at org.glassfish.ejb.startup.SingletonLifeCycleManager.initializeSingleton(SingletonLifeCycleManager.java:213) at org.glassfish.ejb.startup.SingletonLifeCycleManager.initializeSingleton(SingletonLifeCycleManager.java:174) at org.glassfish.ejb.startup.SingletonLifeCycleManager.doStartup(SingletonLifeCycleManager.java:152) at org.glassfish.ejb.startup.EjbApplication.start(EjbApplication.java:150) ... [removed several classes involved in installing the EAR] ... at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57) at com.sun.grizzly.ContextTask.run(ContextTask.java:69) at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:330) at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:309) at java.lang.Thread.run(Thread.java:619) Caused by: javax.ejb.CreateException: Initialization failed for Singleton LookupSessionFacade at com.sun.ejb.containers.AbstractSingletonContainer.createSingletonEJB(AbstractSingletonContainer.java:525) at com.sun.ejb.containers.AbstractSingletonContainer.access$100(AbstractSingletonContainer.java:74) at com.sun.ejb.containers.AbstractSingletonContainer$SingletonContextFactory.create(AbstractSingletonContainer.java:693) ... 36 more Caused by: java.lang.ClassCastException: my.customer.package.shared.datamodel.reference.ActionTypeLookup cannot be cast to my.customer.package.shared.datamodel.reference.CodeLookupValues at my.customer.package.shared.datamodel.persistence.LookupSessionFacadeBean.reloadCommonLookupValues(LookupSessionFacadeBean.java:127) at my.customer.package.shared.datamodel.persistence.LookupSessionFacadeBean.readLookupTables(LookupSessionFacadeBean.java:70) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at com.sun.ejb.containers.interceptors.BeanCallbackInterceptor.intercept(InterceptorManager.java:1006) ... [removed several more classes involved in installing the EAR] ... at com.sun.ejb.containers.interceptors.CallbackChainImpl.invokeNext(CallbackChainImpl.java:61) at com.sun.ejb.containers.interceptors.InterceptorManager.intercept(InterceptorManager.java:390) at com.sun.ejb.containers.interceptors.InterceptorManager.intercept(InterceptorManager.java:373) at com.sun.ejb.containers.AbstractSingletonContainer.createSingletonEJB(AbstractSingletonContainer.java:518) ... 38 more
We see the error on line 20 at deploy time because the application has a @Singleton EJB that also is annotated with @Startup so the server loads it at deploy time. I don’t know if having a non-startup EJB or a non-singleton EJB would make a difference.
It turns out this was the easiest problem to work around — the ClassCastException goes away if you restart the domain. But I never discovered what causes the exception. At first, I thought the problem must be caused by the two classes being loaded by separate classloaders due to the way we had structured the modules in our application. However, both classes are:
- Deployed in the same Java package
- Inside the same JAR
- Packaged and deployed inside the same EAR.
The ClassCastException does not occur all the time. It just suddenly pops up after a series of deploy/undeploy cycles and does not go away until we restart the GlassFish domain.
My best theory so far as to what causes the ClassCastException is it is a bug in GlassFish’s OSGi bundle class loading. The problem appears as if some GlassFish classloader has the parent MappedSuperclass class loaded even after module undeploy but not the child class. Subsequently, when the updated EAR file is deployed again, the new module’s newly assigned Archive classloader sees that the parent class is already loaded but needs to load the child classes from the new EAR files’s JAR. When the code tries a widening assignment of a child entity to a reference type of the parent class — kaboom! — a ClassCastException because the two classes are now unrelated because of the different classloaders.
The GlassFish OSGi implementation gets my suspicion only because EclipseLink is deployed in GlassFish as an OSGi bundle, and the ClassCastException never occurred when using Hibernate, which is deployed as a set of jar files inside the server’s shared library classpath.
Better theories are welcome in the comments. I poked around but found no others reporting the same bug or that this problem has been fixed in a GlassFish update (we are not running the latest updates of v3). This ClassCastException is only an annoyance because the workaround is to restart the GlassFish domain/server. A restart always fixes the problem and it goes away, until after another series of deploy/undeploy cycles.
The second and more vexing problem is with the way EclipseLink synchronizes its entity session cache
with the database.
If I understand the problem correctly from reading various
if you instantiate a new entity object and call an
method before the new,
detached entity has been persisted with
EclipseLink synchronizes all the detached entity states with the database before performing the query.
That synchronization makes sense:
the database is going to need to know about the new rows in the tables if the
query is going to find the correct data.
at least for me,
is that when EclipseLink performs the INSERT statement to persist the unsaved entities,
it does not call the @PrePersist methods on the entity.
I was counting on @PrePersist methods to set values like created-date and last-updated-date,
which cannot be null in our schema.
I can understand that EclipseLink opted not to call @PrePersist methods when it is merely inserting the
data for its convenience and not because
EntityManager.persist() was called.
But the unexpected behavior required some logic change in our entity code
because Hibernate always seemed to call our @PrePersist methods before performing an INSERT on the database.
After the switch to EclipseLink,
we started seeing database constraint violation errors on these @PrePersist columns.
This second problem with EclipseLink, then, cannot be called a bug, but it was unexpected behavior worth documenting here. I assumed incorrectly that @PrePersist methods, by definition, always would be called before an entity got persisted. Wrong.
Those are the only two unusual problems we saw after switching from Hibernate to EclipseLink. The other problems that occurred were some HQL-specific queries failed, but those could all be converted to standard JPQL with a little alteration.
After complaining about EclipseLink issues, I will profess one preference for EclipseLink: The debugging log messages and error log messages seemed clearer and more straightforward with EclipseLink. When one our our HQL queries failed or I needed to trace what queries were being called and when, EclipseLink’s debugging messages just seemed clearer and easier to understand than many of Hibernate’s logging messages. I could see queries more clearly being translated from JPQL to Oracle SQL, and see what values were being bound to query substitution parameters. I do hand it to EclipseLink for making its database interactions more transparent than I am used to with Hibernate. So when we had the @PrePersist problem, at least I was able to debug and diagnose them fairly easily.