Friday, February 06, 2009

JPA and multiple persistence units

I have JPA/EJB3 @Entities in two separate jars that are loaded by my webapp. As strange as this sounds, it's not that easy to define ORM entities in more than one jar file and have your application load them. It seems this is not a common requirement, yet for a really modular application, it seems obvious to me that you might do this.

One supposedly standard way to solve this is to use the jar-file tag in persistence.xml. But it doesn't solve the problem because you have to use absolute URLs, which is not workable for production.

I found the solution in this post, which was to override persistence unit processing. In addition to the following code, I had to set up my Spring configuration in a particular way to make it all work properly:

import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;

/**
* This merges all JPA entities from multiple jars. To use it, all entities must
* be listed in their respective persistence.xml files using the <class> tag.
*
* @see http://forum.springsource.org/showthread.php?t=61763
*/
public class MergingPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor
{
Map<String, List<String>> puiClasses = new HashMap<String, List<String>>();

public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui)
{
List<String> classes = puiClasses.get(pui.getPersistenceUnitName());
if (classes == null)
{
classes = new ArrayList<String>();
puiClasses.put(pui.getPersistenceUnitName(), classes);
}
pui.getManagedClassNames().addAll(classes);

final List<String> names = pui.getManagedClassNames();
classes.addAll(pui.getManagedClassNames());
}
}

To use it, I found that this only works if do all of the following:
  • All your entities need to be mentioned in your persistence.xml files. Normally you don't have to do this is if you set up entity scanning in your Spring config
  • All your persistence units use the same name, and furthermore it has to be the default name, namely "default"
  • You have to use a particular entity manager factory, namely LocalContainerEntityManagerFactoryBean
  • You have to have define your persistence provider in persistence.xml, in our case org.hibernate.ejb.HibernatePersistence
  • Each persistence.xml file needs a unique name, because they are enumerated in the application context.
Here is an example of a persistence.xml:
<?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="default" transaction-type="RESOURCE_LOCAL">

<provider>org.hibernate.ejb.HibernatePersistence</provider>

<!-- Entities have to be enumerated here for MergingPersistenceUnitPostProcessor -->
<class>com.my.Foo</class>
...

Here is an excerpt from my application context. The whole point is that I can now have as many persistence.xml files as I like, in different jar files:
<bean id="pum"
class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
<property name="persistenceXmlLocations">
<list>
<value>classpath*:META-INF/persistence1.xml</value>
<value>classpath*:META-INF/persistence2.xml</value>
</list>
</property>
<property name="persistenceUnitPostProcessors">
<bean class="com.my.MergingPersistenceUnitPostProcessor"/>
</property>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitManager" ref="pum"/>
</bean>

9 comments:

Dan said...

Try adding exclude-unlisted-classes set to true to the persistence-unit section.
You can have a look here: http://java.sun.com/xml/ns/persistence for an explanation...

Julian Sinai said...

Dan, thanks for the comment. However, I don't think you mean it as an alternative to the merging; it's just a way of avoiding having to enumerate the entity classes in persistence.xml.

Henno Vermeulen said...

I was planning on building our domain model in a modular way with multiple Maven modules. Hopefully this will come in very handy!

russell said...

Hi,

Do you have to do anything different with the view filters when using this approach? I'm using the OpenEntityManagerInViewFilter and the approach for multiple persistence units outlined in this blog, however I'm not seeing any output with no error messages. Any idea?

The Jean-Baptiste Family said...

What did you have to do in case where the persistence units where based on two different datasources?

Julian Sinai said...

Russell: I did not have to do anything different with the view filters, it just worked for me.

Jean-Baptiste: I did not have to deal with multiple data sources, but you should be able to do it by declaring a different datasource in each persistence.xml. That is, each persistence.xml would have an entry like:

Rajasekaran said...

You may please refer to the following link, and do let me know if it really helped http://freejavaclass.com/articles/j2ee/hibernate/multiple_persistence_unit_in_jpa.jsp

Lukesterx said...

Thank you, that was helpful. One request though. Please, next time more examples.

Unknown said...

It was informative but if there is a complete example it can help people like me who are just begin the JPA.