Fabrizio Giudici is a Senior Java Architect with a long Java experience in the industrial field. He runs Tidalwave, his own consultancy company, and has contributed to Java success stories in a number of fields, including Formula One. Fabrizio often appears as a speaker at international Java conferences such as JavaOne and Devoxx and is member of JUG Milano and the NetBeans Dream Team. Fabrizio is a DZone MVB and is not an employee of DZone and has posted 67 posts at DZone. You can read more from them at their website. View Full User Profile

NetBeans & JPA with Multiple Persistence.xml Files

05.29.2008
| 59255 views |
  • submit to reddit
If you are developing a large, desktop application which needs to keep a persistent state one of a complex data structure, where perhaps you have also to perform queries, the use of an embedded relational database with an ORM is a good solution. My experience with blueMarine has been quite positive so far with Derby as the embedded database and Hibernate as ORM. Since a few months I'm slowly replacing all the Hibernate-specific code with JPA, to take advantage of annotations and to have more reusable code (for instance, it could be used in other projects with different ORMs such as TopLink, OpenJPA, etc).

As a side note, this code at the moment doesn't use any of the NetBeans RCP specific APIs, so it can be used if you have to deal with multiple JPA configuration files even in other environments.

Now, blueMarine is highly modular; I like modularity in projects for a number of (obvious) reasons and I have more than one hundred modules in it, developed around the NetBeans RCP APIs. Some of these modules contain their own persistence objects (entities) and they should have their own, independent ORM mappings. With Hibernate, this was not a problem: each module had a set of *.hbm.xml files containing the class-table mappings and you could manually load them one by one with this approach:

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

...

SessionFactory sessionFactory;

...

Configuration configuration = new Configuration();
InputStream is = ... get resource input stream for the *.hbm.xml file
configuration.addInputStream(is);
sessionFactory = configuration.buildSessionFactory()

 

The important point is to re-create the sessionFactory every time you modify the configuration, which could be a bit annoying, but in the end it happens only a few times during the initialization of the application (or the first time you dynamically activate a module with a persistent entity inside).

With JPA things change. The ORM mappings are loaded by default from annotations, so there's no need for the *.hbm.xml files, and this is a good simplification; but there is no API for programmatically register persistent entities, that must be declared in a specific persistence.xml file such in this example:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="BLUEMARINE_PU" transaction-type="RESOURCE_LOCAL">
<class>it.tidalwave.catalog.persistence.AttributePB</class>
<class>it.tidalwave.catalog.persistence.CategoryPB</class>
<class>it.tidalwave.catalog.persistence.TagBindingPB</class>
<class>it.tidalwave.catalog.persistence.TagPB</class>
...
</persistence-unit>
</persistence>

 

If you are used to JPA and J2EE this might sound as a little surprise: in J2EE environments you don't need to explicitly list all the entities, they are dynamically discovered by classpath inspection; outside J2EE you need to explicitly list them (I don't know the reason of this difference, perhaps the point is that classpath inspection takes a bit of time and you can afford to pay it when you re-deploy a J2EE application).

As a second note, JPA indeed allows multiple persistence.xml files, but only if they are related to different persistence units (PUs) - a PU is the container of all the configuration, caches and such for a group of persistent objects, and I don't want to have multiple ones, but a single one for the whole application ("BLUEMARINE_PU").

So, what to do? Thanks to the suggestions of Andrei Badea, I've found a solution, with just two minor issues to fix. The steps to perform are:

  1. Scan through the classpath and find all the persistence.xml files;
  2. Manipulate them by means of XML APIs and merge into a single file;
  3. Feed the resulting file to JPA
The complete code is in the ClassLoaderProxy class, that is available through subversion here (use -r 5502, which is the current while I'm typing, as it will be surely refactored in future).

First, our class extends a ClassLoader

public class ClassLoaderProxy extends ClassLoader 
{
...
public ClassLoaderProxy (final ClassLoader parent)
{
super(parent);
}
...
}

The first method we deal with is able to scan the classpath for the JPA configuration files:

    private static final String PERSISTENCE_XML = "META-INF/persistence.xml";

...

private Collection<URL> findPersistenceXMLs (final Filter filter)
throws IOException
{
final Collection<URL> result = new ArrayList<URL>();

for (final Enumeration<URL> e = super.getResources(PERSISTENCE_XML); e.hasMoreElements(); )
{
final URL url = e.nextElement();

if (filter.filter(url))
{
result.add(url);
}
}

return result;
}

The filter parameter is needed because my approach is to have a "master" persistence.xml file, with all the generic configuration, and several "secondary" files that just list the persistent entities inside each module:

    private static final String MASTER_URL_SUFFIX = "it-tidalwave-bluemarine-persistence.jar!/" + PERSISTENCE_XML;

enum Filter
{
MASTER
{
public boolean filter (URL url)
{
return url.toExternalForm().endsWith(MASTER_URL_SUFFIX);
}
},

OTHERS
{
public boolean filter (URL url)
{
return !url.toExternalForm().endsWith(MASTER_URL_SUFFIX);
}
};

public abstract boolean filter (URL url);
}

The MASTER_URL_SUFFIX, as you can see, contains the jar name of the module where the master persistence.xml file is.

Now, the code that gets all the pieces and merge them. Basically, it extracts all the "classes" declarations from the secondary files and inserts them in the master file. I'm not giving details on the implementation, as it is just a JAXP / XPATH exercise:

    private static final DocumentBuilderFactory DOC_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();

private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance();

private static final XPath XPATH = XPATH_FACTORY.newXPath();

private static final XPathExpression XPATH_ENTITY_PU_NODE;
private static final XPathExpression XPATH_ENTITY_CLASS_TEXT;

...

static
{
try
{
XPATH_ENTITY_PU_NODE = XPATH.compile("//persistence/persistence-unit");
XPATH_ENTITY_CLASS_TEXT = XPATH.compile("//persistence/persistence-unit/class/text()");
}
catch (XPathExpressionException e)
{
throw new ExceptionInInitializerError(e);
}
}

...

private String scanPersistenceXML()
throws IOException,
ParserConfigurationException,
SAXException,
XPathExpressionException,
TransformerConfigurationException,
TransformerException
{
logger.info("scanPersistenceXML()");
final DocumentBuilder builder = DOC_BUILDER_FACTORY.newDocumentBuilder();
DOC_BUILDER_FACTORY.setNamespaceAware(true);

final URL masterURL = findPersistenceXMLs(Filter.MASTER).iterator().next();
logger.fine(String.format(">>>> master persistence.xml: %s", masterURL));
final Document masterDocument = builder.parse(masterURL.toExternalForm());
final Node puNode = (Node)XPATH_ENTITY_PU_NODE.evaluate(masterDocument, XPathConstants.NODE);

for (final URL url : findPersistenceXMLs(Filter.OTHERS))
{
logger.info(String.format(">>>> other persistence.xml: %s", url));
final Document document = builder.parse(url.toExternalForm());
final NodeList nodes = (NodeList)XPATH_ENTITY_CLASS_TEXT.evaluate(document, XPathConstants.NODESET);

for (int i = 0; i < nodes.getLength(); i++)
{
final String entityClassName = nodes.item(i).getNodeValue();
logger.info(String.format(">>>>>>>> entity class: %s", entityClassName));

if (i == 0)
{
puNode.appendChild(masterDocument.createTextNode("\n"));
puNode.appendChild(masterDocument.createComment(" from " + url.toExternalForm().replaceAll(".*/cluster/modules/", "") + " "));
puNode.appendChild(masterDocument.createTextNode("\n"));
}

final Node child = masterDocument.createElement("class");
child.appendChild(masterDocument.createTextNode(entityClassName));
puNode.appendChild(child);
puNode.appendChild(masterDocument.createTextNode("\n"));
}
}

return toString(masterDocument);
}

Now, where should we trigger this processing? In the getResource() method of the ClassLoader, which is precisely the one called by JPA to retrieve the persistence.xml file. This method must return the URL of the requested resource and the idea is that we will not return the master file URL, rather the URL of our synthetised file.

private static final String PERSISTENCE_XML = "META-INF/persistence.xml";

private URL persistenceXMLurl;

...

@Override
public Enumeration<URL> getResources (final String name)
throws IOException
{
if (PERSISTENCE_XML.equals(name))
{
if (persistenceXMLurl == null)
{
try
{
final String persistenceXml = scanPersistenceXML();
logger.fine("persistence.xml " + persistenceXml);

// The base directory must be empty since Hibernate will scan it searching for classes.
final File file = new File(System.getProperty("java.io.tmpdir") + "/blueMarinePU/" + PERSISTENCE_XML);
file.getParentFile().mkdirs();
final PrintWriter pw = new PrintWriter(new FileWriter(file));
pw.print(persistenceXml);
pw.close();
persistenceXMLurl = new URL("file://" + file.getAbsolutePath());
logger.info("URL: " + persistenceXMLurl);
}
catch (ParserConfigurationException e)
{
throw new IOException(e.toString());
}
catch (SAXException e)
{
throw new IOException(e.toString());
}
catch (XPathExpressionException e)
{
throw new IOException(e.toString());
}
catch (TransformerConfigurationException e)
{
throw new IOException(e.toString());
}
catch (TransformerException e)
{
throw new IOException(e.toString());
}
}

return new Enumeration<URL>()
{
URL url = persistenceXMLurl;

public boolean hasMoreElements()
{
return url != null;
}

public URL nextElement()
{
final URL url2 = url;
url = null;
return url2;
}
};
}

return super.getResources(name);
}

 

Here you can see the first rough corner. Since I must return an URL (that will be later opened by JPA by opening a connection to it), I must create a physical file to refer it (indeed NetBeans RCP allows to create a "virtual" filesystem which is mapped into memory and it has its own URLs - I attempted to use it, but I've been told that those URLs can't be connected to, so this approach won't work. I think it can be fixed, but it's a matter of a future post).

Well, we're at the end of the job. Now we just have to force JPA to use our ClassLoader, instead of the default one. In blueMarine, I have a specific method of a specific service that is used to access JPA. In this method, I'm just overriding the "system ClassLoader" across the call to JPA, as in the following code:

    public synchronized EntityManagerFactory getEntityManagerFactory()
{
if (entityManagerFactory == null)
{
final Properties properties = configuration.getProperties();
final Thread currentThread = Thread.currentThread();
final ClassLoader saveClassLoader = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(new ClassLoaderProxy(saveClassLoader));
entityManagerFactory = javax.persistence.Persistence.createEntityManagerFactory("BLUEMARINE_PU", configuration.getProperties());
currentThread.setContextClassLoader(saveClassLoader);
}

return entityManagerFactory;
}

This is the second rough corner: while it fits my needs, a better solution would be to try to replace the system class loader globally; in this way, you could call JPA without any specific care. At the moment I don't know whether it's possible to replace the system ClassLoader in NetBeans RCP - matter for another post!

Published at DZone with permission of Fabrizio Giudici, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

David Chamberlin replied on Thu, 2008/05/29 - 6:40am

One potential gotcha about supporting this model is that depending on which modules are loaded, you end up with a different database schema.  For example, if you add a module that contains a model object with a to-one relationship from one of the core modules this would end up requiring an additional foreign key in the core object's table.  This may mean a data migration exercise is needed when you upgrade or add new features that were not part of an initial deployment.

Nevertheless, I think this looks like something that ought to be supported by the specification.  Perhaps you could look to getting it added into JPA 2.0 - you should run it by Mike Keith as a potential enhancement.

Fabrizio Giudici replied on Thu, 2008/05/29 - 6:52am

Excellent remark, David, and I've actually forgotten to say that in my application, in fact, I don't use JPA relationships at all. I can live with it since indeed I have a very simple schema of relationships, in which a single entity is related with entities provided by other modules; thus is not difficult to handle them manually. Of course, in other scenarios this could be a problem.

BTW, managing the database schema upgrades needed by new versions of the software is another problem - in a desktop application, you can't afford to do that with an administration task as you'd probably do in a regular enterprise application, since the upgrade must be performed automatically and in unattended mode when the user installs and runs a new version of the application. I've done some preliminary work in this area that I should be able to post on my blog in a few days.

Dan Hawley replied on Tue, 2009/09/15 - 10:49am

Hi, I am unable to follow the link to the classloader proxy class. I am doing something wrong or is the link out of date? Thanks, Dan

Jogesh Shamra replied on Fri, 2011/01/21 - 6:45am

Thanks for these xml files. yournetbiz is really majestic. i m glad to be a part of it. It is giving me a helping hand for being on line. It is a sort of benchmark for the beginners. It has helped me in my heating and cooling system repairs.

Nathan Dias replied on Sat, 2011/07/30 - 6:30pm

JPA is excelent, this code is very difficult Curso de PHP

Darren Martin replied on Thu, 2011/08/04 - 4:45am

Never had any bad experienced using it, so still I am satisfied with it.

name ideas

Marta Poppinz replied on Thu, 2011/08/04 - 11:11am

thanks you for codes I'll use for this Google Übersetzer

James Black replied on Wed, 2011/08/10 - 8:26am

this code is really working thanks buddy.

 

chiropractor lafayette

Bayu Sangkar replied on Tue, 2011/08/16 - 1:01am

JPA would be great. I t is difficult now days to learn this code. purnail

Nic Side replied on Fri, 2011/08/26 - 8:42am

Thanks for these codes. It is giving me a helping hand for being on line ! I'll use for my annuaire tourisme. See you soon ! thanks

Jeff Lee replied on Sun, 2011/08/28 - 11:54am

Normal 0 false false false EN-US X-NONE X-NONE MicrosoftInternetExplorer4 /* Style Definitions */ table.MsoNormalTable {mso-style-name:"Table Normal"; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-qformat:yes; mso-style-parent:""; mso-padding-alt:0in 5.4pt 0in 5.4pt; mso-para-margin-top:0in; mso-para-margin-right:0in; mso-para-margin-bottom:10.0pt; mso-para-margin-left:0in; line-height:115%; mso-pagination:widow-orphan; font-size:11.0pt; font-family:"Calibri","sans-serif"; mso-ascii-font-family:Calibri; mso-ascii-theme-font:minor-latin; mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi;}

Very nice and creative post .very good writing. I like your writing style and your creativity. The content  is also quite unique. So good job dear.

 

hcg diet Wichita wichita hcg diet

Wak Ngah replied on Thu, 2011/09/29 - 8:49am

real good code.and working..awesome Sharing Community for everyone Thank you

Darren Martin replied on Mon, 2011/10/03 - 7:29am

Some things in here I have not thought about before. I just came here before, now this process really works with me. thanks. Estudiar ingles en el extranjero

Margie Soo replied on Sat, 2011/10/08 - 8:49pm in response to: Dan Hawley

Producing a good and reliable gift baskets desktop application is time consuming because all the codes that goes into it. The correction process is sometimes painstaking but it is necessary so that the program will run smoothly.

Nathan Dias replied on Fri, 2011/10/14 - 1:42pm

which book is very good for learn java? Curso de webdesign

Nathan Dias replied on Fri, 2011/10/14 - 2:02pm

can also use the class simple_html_dom. curso de webdesign

Hushcat Hunny replied on Wed, 2011/12/14 - 2:18pm

Interesting post and thanks for sharing.New Year Greetings Some things in here I have not thought about before.Thanks for making such a cool post which is really very well written. Will be referring a lot of friends about this.christmas cards

Emma Jhon replied on Thu, 2012/01/12 - 12:44am

Hey I am so glad I found your website,I really found you by mistake, while I was browsing on Digg for somethingelse, Anyways I am here now and would just like to say thank you for a marvelous post and a all round thrilling blog (I also love the theme/design), I donâ t have time to browse it all at the moment but I have book-marked it and also included your RSS feeds, so when I have time I will be back to read a great deal more, Please do keep up the awesome work...!

Free Cell Phone Spy

Alex Fusate replied on Mon, 2012/01/23 - 9:06pm

Like Dan said, I am unable to follow the link to the classloader proxy class. I am doing something wrong or is the link out of date? Thanks in advance ! Alex from guitare enfant, guitare electrique enfant

Manuel Escrig replied on Fri, 2012/02/10 - 6:51am

Some things in here I have not thought about before. I just came here before, now this process really works with me. thanks. Courses Around The World

Manuel Escrig replied on Fri, 2012/02/10 - 6:32am

Some things in here I have not thought about before. I just came here before, now this process really works with me. thanks. 

 

Courses Around The World

Austin Computer... replied on Sun, 2012/03/11 - 12:08pm in response to: David Chamberlin

I am highly glad to get this codes in this blog.This was really unexpected.Thanks for sharing this blog.I would like to use this codes in my upcoming projects.

Austin Computer Repair

Alexis Schnee replied on Tue, 2012/03/27 - 4:45am

This is an informative website. Dallas Knee Doctor 3401 West Airport Freeway, Suite 101, Irving, TX 75062-5902

Sudip Kar replied on Sat, 2012/04/07 - 8:51am

Hi,

A very common perception with weightlifting supplements is that they are unnatural. They are necessary nutrients that typically come from a natural food source.  Some people may have deficiency which can cause them to not be able to absorb these nutrients or process them correctly.

 

androgenic anabolic steroids

Martin Sooner replied on Fri, 2012/04/20 - 2:35am

So what is the reason that the virtual file system need its own URL global content

Martin Sooner replied on Thu, 2012/04/26 - 6:13am in response to: Bayu Sangkar

More people are starting to use virtual files because it enables them to access it online and they don’t have to worry about losing their data asphaltene

Carla Brian replied on Sat, 2012/04/28 - 11:00pm

It is an open source application. That's what I like about it. It is very efficient too. - Marla Ahlgrimm

Arthur Bond replied on Sun, 2012/04/29 - 5:46am

Cloud storage are great idea because it allows you to access your file wherever there are online access marietta pressure washing

Jesse Jaackson replied on Thu, 2012/06/07 - 3:11am

The post is really useful resource that will really helpful for me, you are providing amazing stuff, you got so many points here, that's why i always enjoy reading your post..

cell spyware

Pedro Cunha replied on Fri, 2012/07/13 - 3:06pm

Nice post about JPA and Netbeans. Keep doing this good work. Desenvolvendo uma aplicação completa com JPA

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.