Recommended Way To Structure A Spring 3, JPA 2, and Maven Web Application
Introduction
I've created an example application that uses Struts 2, Spring 3, JPA 2, Hibernate 3.5, and Maven. I need some advice from Java web application developers who are using Spring 3, JPA 2, and Maven. The programming team I'm part of is moving to JPA. We plan to use Hibernate as the JPA provider but we want to keep the dependency on Hibernate to a minimum. To reduce our direct dependency on Hibernate we are using the Spring - JPA configuration described in chapter 4 of Spring Persistence with Hibernate and in section 13.5 of the Spring 3 documentation (see references below). I've run into an issue with configuring my applications to follow this structure and I'd appreciate any advice from Java developers more experienced with Spring, JPA, and Maven.
Example Application
Please download the example application. It is a standard Maven project, created using Eclipse 3.6. The application uses an in-memory database (HSQL) that is setup by the Spring applicationContext.xml class. See files schema.sql and test-data.sql in src/main/resources for the schema created during the application's startup and the initial records inserted into the table's database. Using the in-memory database means there is no need to setup and run a separate database server for the example application.
You can import this project into Eclipse by using Eclipse's File - Import feature. In the project's root folder is a README.txt file that explains how to run the application. If you're not using Eclipse with the Maven 2 plugin, you can unzip the archived download and view the source code in any text editor. If you are using a Java IDE that enables importing of Maven projects you should be able to import the unzipped project.
You can also run the application using Maven directly if you have downloaded and installed Maven on your computer. Open a terminal (command) window and navigate to the project's root folder (the folder where pom.xml resides). Then in the terminal window issue these commands:
- mvn clean
- mvn test (All tests should pass successfully)
- mvn jetty:run
When you see [INFO] Started Jetty Server open a web browser and navigate to http://localhost:8080/strutsspringjpaexample/index.jsp. Enter 1, 2, or 3 in the employee id text field and click submit.
To stop the Jetty web server type CTRL-C in the terminal window.
Application Structure
Based on the goal of reducing the direct dependency on Hibernate, the application uses the configuration described in section 13.5 of the Spring 3.0 documentation (reference 2) to setup Spring integration with JPA. The Spring configuration file creates an LocalContainerEntityManagerFactoryBean object and provides it with the data source, location of the persistence.xml file, and the name of the persistence unit to use.
The persistence.xml file defines the persistence unit including what classes are to be JPA entities, what is the JPA provider (Hibernate), and the properties for the JPA provider.
The model classes use JPA 2 annotations to identify how a model class is related to a database table.
The data access classes use Spring's @Repository annotation to mark the class as one Spring should automatically create an instance of in the Inversion of Control container. In the Spring configuration file are these nodes which are used with @Repository:
<!-- scans the classpath for annotated components (including @Repository) that will be
auto-registered as Spring beans -->
<context:component-scan base-package="edu.ku.it.si.springjpaexample.dao" />
<!-- This will ensure that hibernate or jpa exceptions are automatically translated into
Spring's generic DataAccessException hierarchy for those classes annotated with Repository
For example see PersonDaoJpa-->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
The data access classes also use the JPA @PersistenceContext annotation to mark which method Spring should use to inject the EntityManager object into the data access class. The data access classes then use the EntityManager object to create queries that will run against the database and return model objects.
Note that the data access classes do not extend any other class and are not tied to Hibernate. The model and data access classes only use JPA 2 annotations. No Hibernate configuration files are needed. The Hibernate properties are set in the pesistence.xml file. As I understand it as long as our classes use JPA 2 annotations, I should be able to change JPA providers (say from Hibernate to OpenJPA) by just changing the persistence.xml file.
The service classes use Spring's @Service annotation to mark the class as one Spring should automatically create an instance of in the Inversion of Control container. Using the Struts 2 Spring plugin (see the pom.xml) will cause Spring to inject the services classes into the Struts 2 Action Support classes.
In the service classes any data access classes needed are annotated with Spring's @Autowired annotation.
Tomcat Issues With Spring - JPA Project
My company uses Tomcat 6 as its Servlet container. When deploying the war file created by my example application I get this warning:
WARN org.hibernate.ejb.packaging.InputStreamZippedJarVisitor.doProcessElements:61 -
Unable to find file (ignored): jndi:/localhost/strutsspringjpaexample/
java.io.FileNotFoundException
I think this is from a bug in the Hibernate code, as the application works fine. I researched the error on the internet and only found one reference that might be applicable (see reference 3). But if anyone has more insight on why this warning is generated and if there is something I need to configure differently please comment.
Request For Advice
My programming team is exploring the best way to structure our Spring - JPA web applications. I've created an example application that demonstrates one way to structure the application. Before recommending this structure to my team I'd like to get input from more experienced Java developers.
I'd appreciate any advice and comments from other Java developers who have experience with Spring - JPA - Maven web applications on the best way to structure the application.
References
- Spring Persistence with Hibernate; Paul Tepper Fisher and Brian D. Murphy; Apress Publishing, 2010
- Section 13.5 JPA, Spring Framework Reference Documentation 3.0
- Thread Discussion On Hibernate Application Startup
your Struts2-spring-jpa sample works fine and I have the same warning
when it runs on Tomcat. But it doesn't work any longer when I have 3 tables
with manyToMany : any hint ?
inside your persistence.xml file do the following: <persistence-unit name="mylPU" transaction-type="RESOURCE_LOCAL">
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.username" value="USERNAME"/>
<property name="hibernate.connection.password" value="PASSWORD"/>
<property name="hibernate.connection.url" value="jdbc:mysql://ADDERSS"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
this is my only reference to hibernate. other than thay make sure that hibernate 3+ persistence and annotation jars are there. inside of spring appContext.xml i DO have to name a entityManagerFactory, repeat a "dataSource" with the username and password, and then name a transactionManager. it works. the advantage is that i am easily with the javax.persistence and can stay there without having to sweat about hibernate...
In action class u declared service class as transient!! Why we should do that ?
Thanks..
Regards,
Arun.R.U
The Struts 2 class ActionSupport implements Serializable (see http://struts.apache.org/2.2.1/struts2-core/apidoc...). So any instance members of the class must also implement Serializable or be marked as transient (http://download.oracle.com/javase/tutorial/javabea...).
Since the Service classes do not maintain state there is no need for them to implement Serializable.
Thanks for your example.
In the action package, I find one class per action, such as PersonDeleter, PersonFinder, PersonSaver. As the project grows bigger, won't there be too many action classes? Why not put the Person operation related methods in one action class?
Thanks!
Regards,
Chen Li
Your suggestion is one other developers I know do follow.
the application server is unable to locate the strutsspringjpaexample.war. thats why the warning message probably. Not only that at the time of App Server shutdown I found the below message on server.
Nov 19, 2011 1:01:44 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
SEVERE: The web application [/strutsspringjpaexample] created a ThreadLocal with key of type [com.opensymphony.xwork2.inject.ContainerImpl$10] (value [com.opensymphony.xwork2.inject.ContainerImpl$10@c6c033]) and a value of type [java.lang.Object[]] (value [[Ljava.lang.Object;@749130]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
the trace is as follows:
2011-11-19 13:15:30,125 WARN org.hibernate.ejb.packaging.InputStreamZippedJarVi
sitor.doProcessElements:61 - Unable to find file (ignored): jndi:/localhost/stru
tsspringjpaexample/
java.io.FileNotFoundException: jndi:/localhost/strutsspringjpaexample/
at org.apache.naming.resources.DirContextURLConnection.getInputStream(Di
rContextURLConnection.java:382)
at java.net.URL.openStream(URL.java:1035)
at org.hibernate.ejb.packaging.InputStreamZippedJarVisitor.doProcessElem
ents(InputStreamZippedJarVisitor.java:57)
at org.hibernate.ejb.packaging.AbstractJarVisitor.getMatchingEntries(Abs
tractJarVisitor.java:146)
at org.hibernate.ejb.packaging.NativeScanner.getClassesInJar(NativeScann
er.java:128)
at org.hibernate.ejb.Ejb3Configuration.addScannedEntries(Ejb3Configurati
on.java:467)
at org.hibernate.ejb.Ejb3Configuration.scanForClasses(Ejb3Configuration.
java:828)
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:
582)
at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFa
ctory(HibernatePersistence.java:72)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.cr
eateNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:225)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPro
pertiesSet(AbstractEntityManagerFactoryBean.java:308)
at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1477)
at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory.initializeBean(AbstractAutowireCapableBeanFactory.java:1417)
at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory.createBean(AbstractAutowireCapableBeanFactory.java:456)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getOb
ject(AbstractBeanFactory.java:291)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistr
y.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBe
an(AbstractBeanFactory.java:288)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean
(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.
getBeansOfType(DefaultListableBeanFactory.java:398)
at org.springframework.beans.factory.BeanFactoryUtils.beansOfTypeIncludi
ngAncestors(BeanFactoryUtils.java:275)
at org.springframework.dao.support.PersistenceExceptionTranslationInterc
eptor.detectPersistenceExceptionTranslators(PersistenceExceptionTranslationInter
ceptor.java:139)
at org.springframework.dao.support.PersistenceExceptionTranslationInterc
eptor.<init>(PersistenceExceptionTranslationInterceptor.java:79)
at org.springframework.dao.annotation.PersistenceExceptionTranslationAdv
isor.<init>(PersistenceExceptionTranslationAdvisor.java:70)
at org.springframework.dao.annotation.PersistenceExceptionTranslationPos
tProcessor.setBeanFactory(PersistenceExceptionTranslationPostProcessor.java:99)
at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory.invokeAwareMethods(AbstractAutowireCapableBeanFactory.java:1439)
at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory.initializeBean(AbstractAutowireCapableBeanFactory.java:1408)
at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
at org.springframework.beans.factory.support.AbstractAutowireCapableBean
Factory.createBean(AbstractAutowireCapableBeanFactory.java:456)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getOb
ject(AbstractBeanFactory.java:291)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistr
y.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBe
an(AbstractBeanFactory.java:288)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean
(AbstractBeanFactory.java:194)
at org.springframework.context.support.AbstractApplicationContext.regist
erBeanPostProcessors(AbstractApplicationContext.java:710)
at org.springframework.context.support.AbstractApplicationContext.refres
h(AbstractApplicationContext.java:410)
at org.springframework.web.context.ContextLoader.createWebApplicationCon
text(ContextLoader.java:276)
at org.springframework.web.context.ContextLoader.initWebApplicationConte
xt(ContextLoader.java:197)
at org.springframework.web.context.ContextLoaderListener.contextInitiali
zed(ContextLoaderListener.java:47)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContex
t.java:4723)
at org.apache.catalina.core.StandardContext$1.call(StandardContext.java:
5226)
at org.apache.catalina.core.StandardContext$1.call(StandardContext.java:
5221)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.
java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor
.java:603)
at java.lang.Thread.run(Thread.java:722)
I'm not sure about injecting a dependency into a Servlet filter.
You may also want ask your question in the Spring user forum.
I have another question. I dont why with thissetup I am not able to get result for native or jpql query having cos() acos() functions ... any jar dependecy missing ?
This article is a bit old now. Do yo have any most up-to-date integration structure and code now?
Thanks,
Yoichi