Turgay Zengin
Java & JEE notes
Thursday, September 20, 2007
Multiple Persistence Units with Spring, JPA, Tomcat - Update
When the bean called org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
is defined in Spring application context, Spring was trying to set up all persistence units when even only one of them was accessed for an operation - "Login to the system" use case, for example. When one of the databases was not accessible, the SQLException was causing Spring to give up and not load the other persistence units as well. At that time we were using Spring version 2.0.6.
Going to Spring support forums for a solution to this problem, I couldn't find any help but saw that Spring 2.1-M4 was released and decided to give it a try. Changing spring jars with the new version, the error disappeared - I guess this issue was resolved in this version of Spring. Now if one database is not accessible, only the DAOs which needs that @PersistenceContext is affected - other DAOs work as expected.
So, Spring 2.1-M4 or later should be used when more than one persistence unit will be defined.
Tuesday, September 11, 2007
Multiple Persistence Units with Spring, JPA, Tomcat
I was struggling with using Spring and JPA on Tomcat some time ago, but then could not manage and turned to EJB3 and Glassfish for my latest project.
EJB3 is far better than before, but it is not very easy to manage an application server and an "Enterprise Application". I had 3 projects in Eclipse: The enterprise application project, EJB and web application projects. Also, managing an application server is not as easy as managing Tomcat, considering the small size of my team (2 developers). Development - deployment - testing cycle is not easy, as we have to re-publish our application after every small change to JSP files or Java classes. EJB3 and Glassfish (or any other appserver) is also a powerful combination, but I was missing Spring and Tomcat.
We were half way through our development, and I decided to give another try to using Spring+JPA under Tomcat. This time, I found a blog in which this setup was described. Applying those steps and making some changes, I was successful to make it work and converted the application to Spring+JPA+Tomcat 6 in the weekend! We have more than one persistence units defined. Now we feel much confident that we will finish the project on time.
Here is what I did:
- In src/META-INF/persistence.xml define the persistence units:
<persistence-unit name="pu1" type="RESOURCE_LOCAL">
....
</persistence-unit>
<persistence-unit name="pu2" type="RESOURCE_LOCAL">
....
</persistence-unit>
- In META-INF folder (under the web application root, not the one in src folder which goes under WEB-INF/classes), create a file called context.xml. The "Loader" definition is necessary for Spring+JPA under Tomcat, and the "Resource" elements define JNDI entries:
<Context>
<Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"
useSystemClassLoaderAsParent="false" />
<Resource name="jdbc/db1" auth="Container" type="javax.sql.DataSource"
maxActive="10" maxIdle="5" maxWait="15000"
removeAbandoned="true" removeAbandonedTimeout="300" logAbandoned="true"
initialSize="2" minIdle="2" username="username1" password="password1"
driverClassName="oracle.jdbc.driver.OracleDriver"
url="jdbc:oracle:thin:@localhost:1521:xe"/>
<Resource name="jdbc/db2" auth="Container" type="javax.sql.DataSource"
maxActive="10" maxIdle="5" maxWait="15000"
removeAbandoned="true" removeAbandonedTimeout="300" logAbandoned="true"
initialSize="2" minIdle="2" username="username2" password="password2"
driverClassName="oracle.jdbc.driver.OracleDriver"
url="jdbc:oracle:thin:@localhost:1521:xe"/>
</Context>
- In web.xml, define "resource-ref" entries for db connections:
<resource-ref>
<description>DB Connection 1</description>
<res-ref-name>jdbc/db1</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<resource-ref>
<description>DB Connection 2</description>
<res-ref-name>jdbc/db2</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
- Spring ContextLoaderListener must also be present in web.xml:
<listener>
<listener-class> org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
- Beans in Spring application context look like:
<!-- JPA annotations bean post processor -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor">
<!-- Exception translation bean post processor -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor">
<tx:annotation-driven manager="transactionManager1">
<tx:annotation-driven manager="transactionManager2">
<bean id="dao1" class="dao.DAO1JPA">
</bean>
<bean id="dao2" class="dao.DAO2JPA">
</bean>
<bean id="service1" class="service.Service1Impl">
<property name="dao" ref="dao1">
</bean>
<bean id="service2" class="service.Service2Impl">
<property name="dao" ref="dao2">
</bean>
<bean id="entityManagerFactory1" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="pu1">
<property name="dataSource" ref="dataSource1">
<property name="jpaVendorAdapter">
....
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver">
</property>
</bean>
<bean id="entityManagerFactory2" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="pu2">
<property name="dataSource" ref="dataSource2">
<property name="jpaVendorAdapter">
....
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver">
</property>
</bean>
<bean id="dataSource1" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/db1">
</bean>
<bean id="dataSource2" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/db2">
</bean>
<bean id="transactionManager1" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory1">
<property name="dataSource" ref="dataSource1">
</bean>
<bean id="transactionManager2" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory2">
<property name="dataSource" ref="dataSource2">
</bean>
- Let Spring inject EntityManager instances into DAO classes:
@PersistenceContext(unitName = "pu1")
private EntityManager emPU1;
- Database jars, spring-tomcat-weaver.jar and toplink-essentials.jar are placed in Tomcat/lib folder. Spring jars (spring.jar, spring-aspects.jar, spring-mock.jar) are placed in WEB-INF/lib.
- In Tomcat6/lib folder, annotations-api.jar contains classes which causes exceptions. From that jar, delete package "javax.persistence" to get rid of those exceptions. (Found this info here)
With this setup, develop - deploy - test cycle is much easier now. We also have access to all Spring goodies now: Easy e-mail sending, defining jobs with Quartz, JMX exporter, ....
(Code and XML formatting is not easy here, or I don't know how to format better on blogger. Any suggestions?)