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?)