
|
Java Persistence API 1.0 Gotchas![]() Source BigFoto.com
This tutorial aims to address two aspects of using the JPA 1.0; understanding the missing functionality, and providing a work around. Understanding what's missing is important for a couple of reasons; firstly to know when you'll have to use vendor specific annotations (or metadata mapping files), secondly to estimate the cost of transferring your ORM domain model to a different vendor.
The examples and code shown here refer exclusively to Hibernate as the vendor ORM beneath JPA 1.0, the actual project is currently using Hibernate, OpenJPA, Oracle TopLink Essentials and EclipseLink. In addition, the test harness code uses Spring.
The Missing InformationWell, missing functionality, really. As much as I respect and rejoice the efforts made by the JPA 1.0 committee in levelling the ground for ORM domain model development, I would have preferred that at least some information was available explaining which aspects of ORM modelling have not been addressed by JPA 1.0. I have not been able to find such information directly – but that could be just me.
From various sources, including the JPA 2.0 specifications, blogs, articles, and an interesting thread on the JUG Trento mailing list, the following, almost certainly incomplete, list has been compiled:
Additionally, the JPA 1.0 has some features 'considered harmful', at least by Patrick Linskey, such as globally scoped queries (which makes local scoped queries difficult in a future version).
The Work AroundsAs we're all good and seasoned programmers, we've come to realise that nothing is perfect – except, maybe, our own code. So the first thing to do is explore the extensions available to us.
All of the vendor ORMs provide their own specific annotations, which resolve the problems listed previously. If you're only ever going to use one specific vendor ORM, this is certainly the least painful path.
On the other hand, if you're developing a product where more than one vendor ORM will eventually be required, then your domain model code is going to become pretty messy, as in this - admittedly contrived - pseudo-code example:
@Column(...) @HibernateExtension(...) @OpenJPAExtension(...) @EclipseLinkExtension(...) @UncleTomCobleyExtension(...) String myPoorLittleProperty; // Here I am... Personally, I think that's a lot of noise, and not much signal. It is however, still a solution.
An interesting alternative, provided by JPA 1.0, is the partial metadata mapping file. In theory, you can add further, partial, mapping information in a separate XML file, which will be added to, or replace, the information already supplied by the annotations. Unfortunately, the specifications (or rather the XSD) for this metadata mapping file does not allow for vendor specific tags. I certainly haven't seen anything in the Hibernate (nor OpenJPA, and EclipseLink) manuals explaining how to add, say, Hibernate specific elements to the XML file.
Hibernate also provides an ORM metadata XML based mapping file, but unfortunately, it does not have a partial mechanism. So you would have to repeat the entire entity mapping, just to add, say, the delete-orphan cascade type.
The Third OptionWhich leaves us with the third option – programmatic modification. The idea is to make programmatic changes to the annotations and / or XML mapping information metadata before the EntityManagerFactory is created. The metadata must be modified before the EntityManagerFactory is created because, after creation, the metadata is considered read-only.
All vendor specific metadata modifications can then be isolated in one or more classes. These classes can then be replicated for other vendor ORMs (or at least the cost of doing so can be quantified).
Using Spring, we start with a concrete class of the org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter class, which for Hibernate is the HibernateJpaVendorAdapter class. Much as I would have liked to simply inject a modified HibernatePersistence object in this class, via Spring itself, my many attempts failed, possibly because the private persistenceProvider property is also final. So I had to sub class it:
package it.syger.jpa;
import javax.persistence.spi.PersistenceProvider;
/**
* Extension to the original HibernateJpaVendorAdapter class to allow
* a sub class of the HibernatePersistence class to be used.
*
* @author john.leach
*/
public class HibernateJpaVendorAdapter extends
org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter {
private PersistenceProvider provider;
public void setPersistenceProvider(PersistenceProvider provider) {
this.provider = provider;
}
public PersistenceProvider getPersistenceProvider() {
return this.provider;
}
}
The org.hibernate.ejb.HibernatePersistence class does not provide an overrideable hook method where the Ejb3Configuration object can be modified before the EntityManagerFactory is created. So again I had to sub class it, but worse, I had to copy the two different createEntityManagerFactory methods, to add the postConfigure method. Not something to be proud of, but I did not see any alternative:
package it.syger.jpa;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import javax.persistence.spi.PersistenceUnitInfo;
import org.hibernate.ejb.Ejb3Configuration;
/**
* Extension to the original HibernatePersistence class to allow
* the configured metadata to be programmatically modified before
* the EntityManagerFactory is created.
*
* This corresponds to a third configuration level, after class
* annotations, and orm.xml or *.hbm.xml files.
*
* @author john.leach
*/
public class HibernatePersistence extends
org.hibernate.ejb.HibernatePersistence {
private HibernateConfigurationBuilder builder;
public void setHibernateConfigurationBuilder(HibernateConfigurationBuilder builder) {
this.builder = builder;
}
public EntityManagerFactory createEntityManagerFactory(String persistenceUnitName, Map overridenProperties) {
Ejb3Configuration cfg = new Ejb3Configuration();
Ejb3Configuration configured = cfg.configure( persistenceUnitName, overridenProperties );
postConfigure(configured);
return configured != null ? configured.buildEntityManagerFactory() : null;
}
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {
Ejb3Configuration cfg = new Ejb3Configuration();
Ejb3Configuration configured = cfg.configure( info, map );
postConfigure(configured);
return configured != null ? configured.buildEntityManagerFactory() : null;
}
/**
* Allow for programmatic modification of the configuration,
* before the EntityManagerFactory is created.
*
* @param cfg the configuration.
*/
public void postConfigure(Ejb3Configuration cfg) {
if (builder != null) {
builder.setConfiguration(cfg);
builder.postConfigure();
}
}
}
With these two classes in place, we can now use Spring to inject a sub class of it.syger.jpa.HibernateConfigurationBuilder which will make the required programmatic configuration changes.
This following code snippet adds delete-orphan to the User domain class:
package it.syger.example.domain;
import org.hibernate.ejb.Ejb3Configuration;
import it.syger.jpa.HibernateConfigurationBuilder;
/**
* Additional modifications, Hibernate specific.
*
* @author john.leach
*/
public class HibernateConfiguration extends HibernateConfigurationBuilder {
public void postConfigure() {
configureUser();
}
private void configureUser() {
String className = "it.syger.example.domain.User";
setPropertyCascade(className, "clients", "all-delete-orphan");
}
}
Remove the call to the configureUser() method, and some unit tests will fail.
So far I have been able to achieve the same effect, but in a much less brittle and disruptive fashion, for EclipseLink. Achieving the same feat with OpenJPA is proving to be a much harder task, which I'm still working on.
Caveat EmptorAs you can see from the above examples, this third solution is fragile and dangerous. I will have to keep a close eye on the Spring framework, and Hibernate Entitymanager otherwise the code may break in future releases.
It may be possible to convince both Spring and Hibernate to modify their own code, but I'd rather wait to see if this solution is deemed acceptable (or interesting) by more than one person before making a formal request.
The Third Option, Take TwoThe code shown above is, well, just plain ugly. Based on the excellent persistence.xml property mechanism provided by Oracle TopLink Essentials and EclipseLink I have substituted it with a similar mechanism for Hibernate, and added this code to the Hibernate issue tracking system (EJB-360) in the hope that it will eventually find its way into the code base.
The PlayersThe most important players seem to be Hibernate, Oracle TopLink Essentials, OpenJPA, and EclipseLink. There are also other runners, such as Cayenne, JPOX, and JDO, but they don't seem to have such an important market share.
For your information:
JPA 2.0 – the Next GenerationThe JCP (JSR 317) web page. Coming to your IDEs towards the end of 2008. See the film, which specifically talks about criteria, collections, and ordered lists.
The JPA 2.0 committee are already hard at work, and will almost certainly address some of the grey areas presented in this article. I doubt, however, that they will be able to address them all.
When the new JPA 2.0 implementations become available, I will revise this tutorial.
The Third Option CodeThe results of this short expedition into JPA consists of a simple domain model, the Hibernate specific 'programmatic configuration' classes, and a series of simple unit tests. The Source code is available here, released under the Apache version 2.0 license, but you will have to install the required libraries for yourself. The library installation process is explained in the README file, and requires three downloads, including the mighty (79 MB) Spring framework download. You have been warned.
The JavaDocs and unit test results are included in the download, for those of you (like myself) who'd rather take a quick look at the contents, before investing any further time actually compiling and running the code for themselves.
The project is a simple test harness. There is plenty of room for improvement. I will be very happy to receive any criticisms or improvements you might wish to supply, if accompanied by a beer.
Nota BenePer chi parla italiano, il progetto ha adesso un suo sito, cortesia della JUG di Torino: Spikes.
Special ThanksThis mini project was put together with the help of several members of the Java Users Group Trento, and the Java Users Group Torino, Italy. Grazie mille, ragazzi. A particular thank you to Simon Bordet, and Nicola Pedot.
The parancoe-yaml.jar file included in the download comes from the JUG Padova Parancoe project sources. They seem to have been the first to use this underrated format for fixtures on the Java platform, though it is in common use in Ruby on Rails. The jar file simply contains the parancoe-yaml classes from that project, which are based on a modified version of JYaml. Many thanks to Lucio Benfante, and Paolo Donà and Michele Franzin of SeeSaw.
ContactsSyger can be contacted for consultancy work on any of the topics mentioned in this article, by sending an email to info@syger.it.
|
Tag cloud: |