Evaluating EclipseLink 1.1

As I’m using the ubiquitous Hibernate 3.3 as the JPA 1.0 provider for Spincloud, I decided to try out another one. I had tried OpenJPA (spawned from Kodo JDO) when they only supported build-time bytecode enhancement and it was a pain to make it work. It worked all right but boy what a pain. There’s now an agent to provide on-the-fly enhancement but I’ll take transparent enhancement anytime.
I’ve heard about EclipseLink before. The project started when Oracle donated the respectable TopLink project to the Eclipse foundation. If the solid reputation behind TopLink was a good enough argument for me to try it, the announcement that it will be the JPA 2.0 reference implementation convinced me that I should try it out.
My goal is to evaluate if EclipseLink is production-ready. I’m applying a complex set of evaluation criteria (joking): if it can run Spincloud then it is (I was inspired by Seifer’s interview on Infoq about Ruby VMs; when asked what was the criteria for qualifying if a Ruby VM is production ready, he answered: if it runs Rails).

I have the following JPA requirements:
– column mappings, one-to-one, one-to-many
– supports BLOB fields
– supports NamedQueries and NamedNativeQueries
– support for object cache and query cache
– deployment/operational nice to have: ease of maintaining compatibility with both EclipseLink and Hibernate in the source code and runtime. Ideally I should plug-in any JPA provider without changing a single line of code. This was not attainable as I’ll explain below.

I started by downloading the binaries. I’m using Maven to bring the jars so I’ve followed the instructions here. I only changed the version since I wanted to use v1.1.0:

  <dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>eclipselink</artifactId>
    <version>1.1.0</version>
    <scope>provided</scope>
  </dependency>

There’s a single jar file called eclipselink-1.1.0.jar downloaded which is a nice change from the multitude of Hibernate jars I was accustomed with.

Next, I created a new persistence.xml file since I didn’t know how different it would be from the one tied to Hibernate. Initially I just changed the JPA provider to:

  <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

as the documentation stated.
Then I had a look at my current NamedQueries and NamedNativeQueries that looked like this:

@org.hibernate.annotations.NamedQueries({
	@org.hibernate.annotations.NamedQuery(name="findStationByPK",
	        query="select station from Station station where station.id=:stationPK"
	        , cacheable=true, cacheRegion="weatherStationsRegion"),
...

and I recalled that I did this in order to make use of Hibernate’s Query cache facility, alas a non-standard way of cache enablement (not until JPA 2.0 anyway). I had to put query caching to rest and switched all my Named*Queries to standard JPA:

@javax.persistence.NamedQueries({
	@javax.persistence.NamedQuery(name="findStationByPK",
	        query="select station from Station where id=:stationPK"),
...

I’m not using the integration unit tests I have due to the issues I had with the Spring integration test framework I’m using after switching to Spring 3.0 so I’m using ad-hoc testing for now. After rebuilding/deploying the project I didn’t notice any error on Tomcat start-up so I just tried the GUI, displaying the weather in a map area. I hit the first error:

Exception [EclipseLink-7060] (Eclipse Persistence Services - 1.1.0.r3639-SNAPSHOT): 
org.eclipse.persistence.exceptions.ValidationException
Exception Description: Cannot acquire data source [java:comp/env/ds/MeteoDS].
Internal Exception: javax.naming.NamingException: This context must be accessed 
throughout a java: URL

I dug through the documentation (what’s with this documentation and what does “No dynamic weaving (instrumentation) – static weaving of entities is still available via EclipseLink” means, why JPAEclipseLinkSessionCustomizer example is broken and finally why use teh deprecated SessionCustomizer?) and found that I have to use a session customizer to make it work in Tomcat. I didn’t like this one bit (it just works in Hibernate) but I followed the instructions and EclipseLink could establish the connection with the Tomcat-configured data source as expected. This was the first duly noted turn-off.
I redeployed and browsed the weather map just to hit another problem:

  Exception Description: Syntax error parsing the query [findStationByPK: 
    select station from   Station where id=:stationPK], line 1, column 28: 
    syntax error at [where].
 Internal Exception: MismatchedTokenException(65!=66)

Hmm, no compile-time JPA query check. Not good. I checked the JPA query, it was:

  @javax.persistence.NamedQuery(name="findStationByPK",
       query="select station from Station where id=:stationPK"),
...

which looked OK but looking closely I realized that it was not. The correct syntax is (notice the station. indirection:

@javax.persistence.NamedQueries({
	@javax.persistence.NamedQuery(name="findStationByPK",
	        query="select station from Station station where station.id=:stationPK"),
	@javax.persistence.NamedQuery(name="findByStationId",
...

Hibernate was happy with the “non-standard” syntax while EclipseLink is not. No big deal, it wasn’t in many places anyway. I scanned all my queries and fixed them. Redeployed and hit this:

  Exception Description: Syntax error parsing the query [findByStationId: from
    Station station where station.stationId=:stationId], line 1, column 0: 
    unexpected token [from].

My JPA query is:

	@javax.persistence.NamedQuery(name="findByStationId",
	        query="from Station station where station.stationId=:stationId")

Again a Hibernate artifact. H3 supports the convenience of using queries that start with “from entity…” if they return entities (single or lists). The fix was simply to add “select station” in front of the JPA QL:

	@javax.persistence.NamedQuery(name="findByStationId",
	        query="select station from Station station where 
                station.stationId=:stationId")

Not a biggie either, standard is better and Hibernate can parse this kind of query. Again, rebuilt/redeployed and queried the map again. This time the error was this:

[EL Warning]: 2009-03-21 19:02:42.135--UnitOfWork(2001070194)--Exception [EclipseLink-4002] 
(Eclipse Persistence Services -
 1.1.0.r3639-SNAPSHOT): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 
You have an error in your SQL syntax; check 
the manual that corresponds to your MySQL server version for the right syntax to use 
near ':area) , LOCATION) and obs_time 
> :minSynopDateToConsiderStr UNION select distin' at line 1
Error Code: 1064
Call: select distinct STATION_FK as STATION_IDENTIFIER, LOCATION,
obs_time as REPORT_TIME  from SYNOP where Contains(GeomFromText(:area) , LOCATION) and 
obs_time > :minSynopDateToConsiderStr UNION 
select distinct STATION_FK as STATION_IDENTIFIER, 
LOCATION, REPORT_TIME as REPORT_TIME from METAR where 
Contains(GeomFromText(:area) , LOCATION) and REPORT_TIME > :minMetarDateToConsiderStr

I couldn’t find anything at first in the documentation (getting really pissed at the documentation style, can’t find anything useful) but I finally found this:

Support for the EclipseLink # convention is helpful if you are already familiar with
EclipseLink queries or if you are migrating EclipseLink queries to a JPA application.

I’m not “already familiar with EclipseLink queries” and no, it’s not helpful. So now I have to use #area instead of :area named parameter. This is really smart. Why in the world would one want to change the column with sharp? I’m starting to suspect clumsiness or plain careless coding. I still want to maintain Hibernate/EclipseLink pluggability so I have no choice but creating a static class that holds all NamedNativeQueries and parametrizes the named parameter prefix:

class NativeQueries {
public final static String WEATHER_SNAPSHOT_IN_AREA_NATIVE_QUERY ="select distinct 
STATION_FK as STATION_IDENTIFIER, LOCATION, obs_time as REPORT_TIME from SYNOP where 
Contains (GeomFromText(@named-param-prefix@area) " +
 ", LOCATION) and obs_time > @named-param-prefix@minSynopDateToConsiderStr UNION " +
"select distinct STATION_FK as STATION_IDENTIFIER, LOCATION, REPORT_TIME
as REPORT_TIME from METAR where Contains(GeomFromText(@named-param-prefix@area) " +
", LOCATION) and REPORT_TIME > @named-param-prefix@minMetarDateToConsiderStr";
...
}

Then I have to use ant to perform a pre-compilation task that replaces @named-param-prefix@ with either : or #. This really got the best of me as I’ve hit a Java compilation quirk too: you have to delete all classes that rely on the NativeQueries class since they will not recompile if the values of the public static final String values change; you have to delete all .class files and force a full compilation for them.
After I did this build change it all started working. Victory at last. Or was it? Well, no; I still have to add query caching as I heavily use spatial queries to fetch area weather (activated every time the map is dragged/zoomed). There’s the @Cache but it’s for entity caching which is good but what I also need is query caching à la Hibernate:

 @org.hibernate.annotations.NamedQuery(name="qName", query="<query>",
    cacheable=true, cacheRegion="some-cache-region")

After another round of thorough search of the documentation I found what I needed but it wasn’t what I expected. EclipseLink doesn’t support annotation-level query cache configuration. Instead you have to code-in (or use AOP to) cache writes after the queries are executed, via its ReadQuery class.
Needless to say I didn’t do it. I gave up on it right there.
On a whole this felt a lot like my (undocumented) evaluation I did over a year ago with OpenJPA (no dice too). Poor documentation (worse than EL’s) and lack of (then) runtime weaving made it a nightmare to deploy. I haven’t tried it ever since.

Prognosis: negative

This is really a shame, EclipseLink seems to be quite fast and has a footprint similar to Hibernate but:
– the documentation is awful. Guys, look at Hibernate and follow their lead. I badly need a single page HTML up-to-date documentation that I can freely browse. I can find almost anything in a matter of seconds using Hibernate’s single page HTML doc and Ctrl-F. I feel so strongly about this, I’d make an addition to the Software Craftsmanship Manifesto: “Maintain top-notch documentation”. Spring and Hibernate projects got it right and this is one of the secrets of their success. Take note.
– Use : (column) for named parameters in NamedNativeQuery; it is consistent with NamedQuery syntax (and hopefully standardized in JPA 2.0).
– Use compile-time Named*Query syntax check. It adds a great deal to predictability.
– Add the Session Customizer fix into of the base platform and make it transparent to the user. Nobody wants to write integration code to fix oddities of popular containers. “Out of the box” is valuable.
– Work on a better query caching support or push hard for a JPA 2.0 beta since there’s standardized second-level caching.

Oh and guys, some documentation pages don’t render properly on Safari, the TOCs overlap the content. Unacceptable.

Until then I’m sticking with Hibernate 3.3. It just works.