JSF-Spring-JPA is the popular stack of choice these days, mostly to be used in my consulting and training purposes I’ve created a base project called MovieStore demonstrating the annotation-driven integration of JSF-Spring-JPA. JSF backing beans, spring service level beans and DAO’s are configured and integrated with annotations. Only the core infrastructure like datasource, entityManagerFactory or transactionManager are configured with xml.
Following are the package contents;
- JSF (MyFaces 1.2.2 with Facelets)
- Spring 2.5
- JPA with Hibernate
- HSQLDB for persistence
- Jetty for deployment
- Maven2 for build
The example application is simple; a MovieStore(Yes, I’m a movie maniac so all my examples are about movies)
There are two formats DVD and BLU-RAY, not including HD-DVD because it’s dead.
Movie
package com.prime.tutorial.moviestore.domain;
//imports not displayed
@Entity
public class Movie implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Basic
private String title;
@Basic
private Integer discs;
@Enumerated
private Format format;
public Movie() {}
public Movie(String title, Integer discs) {
this.title = title;
this.discs = discs;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getDiscs() {
return discs;
}
public void setDiscs(Integer discs) {
this.discs = discs;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Format getFormat() {
return format;
}
public void setFormat(Format format) {
this.format = format;
}
}
For DAO part, I’ve included a GenericDAO too for the crud operations;
package com.prime.tutorial.moviestore.dao;
//imports are not displayed
public interface GenericDAO<t, ID extends Serializable> {
T loadById(ID id);
void persist(T entity);
void update(T entity);
void delete(T entity);
List<t> loadAll();
}
Following is the GenericDAO implementation with JPA, note how the entityManager is injected. Instead of extending JPATemplate, with @PersistenceContext, the entityManager is resolved by spring. This way you don’t need to subclass JPATemplate.
package com.prime.tutorial.moviestore.dao;
//imports are not displayed
public abstract class GenericDAOWithJPA<t, ID extends Serializable> implements GenericDAO<t,ID> {
private Class<t> persistentClass;
@PersistenceContext
protected EntityManager entityManager;
@SuppressWarnings("unchecked")
public GenericDAOWithJPA() {
this.persistentClass = (Class<t>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
public Class<t> getPersistentClass() {
return persistentClass;
}
public T loadById(ID id) {
return entityManager.find(persistentClass, id);
}
public void persist(T entity) {
entityManager.persist(entity);
}
public void update(T entity) {
entityManager.merge(entity);
}
public void delete(T entity) {
entityManager.remove(entity);
}
@SuppressWarnings("unchecked")
public List<t> loadAll() {
return entityManager.createQuery("Select t from " + persistentClass.getSimpleName() + " t").getResultList();
}
}
That’s all about the GenericDAO, for movie specific database operations here comes the MovieDAO.
MovieDAO
package com.prime.tutorial.moviestore.dao;
//imports not displayed
public interface MovieDAO extends GenericDAO{
public List findByTitle(String title);
}
MovieDAOWithJPA is annotated with @Repository, by this way spring can manage it and can also allows ExceptionTranslation(PersistenceAnnotationBeanPostProcessor also needs to be configured in applicationContext.xml).
package com.prime.tutorial.moviestore.dao;
//imports not displayed
@Repository
public class MovieDAOWithJPA extends GenericDAOWithJPA implements MovieDAO{
@SuppressWarnings("unchecked")
public List findByTitle(String title) {
Query query = entityManager.createQuery("Select m from Movie m where m.title = ?1");
query.setParameter(1, title);
return query.getResultList();
}
}
At service level there are two classes, MovieService and MovieServiceImpl.
MovieService - Notice the @Transactional annotation, this way transactions are configured with annotations.(<tx:annotation-driven /> also needs to be declared to enable this, see applicationContext.xml)
package com.prime.tutorial.moviestore.service;
//imports not displayed
public interface MovieService {
public void createNew(Movie movie);
public List findAll();
public List findByTitle(String title);
}
MovieServiceImpl - @Service annotation makes this a spring bean, and movieDAO is injected by type using @AutoWired
package com.prime.tutorial.moviestore.service;
//imports not displayed
@Service
public class MovieServiceImpl implements MovieService{
private MovieDAO movieDAO;
@Autowired
public MovieServiceImpl(MovieDAO movieDAO) {
this.movieDAO = movieDAO;
}
@Transactional
public void createNew(Movie movie) {
movieDAO.persist(movie);
}
public List findAll() {
return movieDAO.loadAll();
}
public List findByTitle(String title) {
return movieDAO.findByTitle(title);
}
}
Finally the JSF Bean and Facelets page to use the stuff above, the page is used for creating new movies.
createMovie.xhtml
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="template.xhtml">
<ui:define name="content">
<h:messages showDetail="true"/>
<h:form>
<h:panelGrid columns="2">
<h:outputLabel for="title" value="Title"></h:outputLabel>
<h:inputText id="title" value="#{createMovie.movie.title}"></h:inputText>
<h:outputLabel for="discs" value="Number of Discs"></h:outputLabel>
<h:inputText id="discs" value="#{createMovie.movie.discs}"></h:inputText>
<h:outputLabel for="format" value="Format"></h:outputLabel>
<h:selectOneMenu id="format" value="#{createMovie.movie.format}">
<f:selectItem itemLabel="DVD" itemValue="DVD" />
<f:selectItem itemLabel="BLU-RAY" itemValue="BLURAY" />
</h:selectOneMenu>
</h:panelGrid>
<h:commandButton value="Save" action="#{createMovie.save}"></h:commandButton>
</h:form>
</ui:define>
</ui:composition>
CreateMovie is the backing bean to handle this page, also managed by spring. The MovieService is set through construction injection, one thing you cannot do in plain jsf ioc. @Component marks this bean as a spring bean with the “createMovie” name. In jsf views, this is the name to be resolved in el expressions like #{createMovie.movie.title}. @Scope is set to request, as you may already know with spring it’s also possible to provide custom scopes.
package com.prime.tutorial.moviestore.view;
//imports not displayed
@Component("createMovie")
@Scope("request")
public class CreateMovie implements Serializable{
private MovieService movieService;
private Movie movie = new Movie();
@Autowired
public CreateMovie(MovieService movieService) {
this.movieService = movieService;
}
public Movie getMovie() {
return movie;
}
public void setMovie(Movie movie) {
this.movie = movie;
}
public String save() {
movieService.createNew(movie);
FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_INFO, "Movie is saved successfully", "OK");
FacesContext.getCurrentInstance().addMessage(null, facesMessage);
movie = new Movie();
return null;
}
}
So where’s the xml? Let’s start with the spring config.
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:property-placeholder location="classpath:application.properties"/>
<context:component-scan base-package="com.prime.tutorial.moviestore" />
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="moviestore"/>
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="${database.dialect}"/>
<property name="showSql" value="${database.showSql}" />
<property name="generateDdl" value="${database.generateDdl}" />
</bean>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close">
<property name="driverClassName" value="${database.driver}"/>
<property name="url" value="${database.url}"/>
<property name="username" value="${database.username}"/>
<property name="password" value="${database.password}"/>
</bean>
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>
Notes on spring config;
- <context:component-scan base-package=”com.prime.tutorial.moviestore” /> scans the moviestore sub packages for annotated classes, with this it can find our jsf beans, services, daos and etc.
- <tx:annotation-driven transaction-manager=”txManager”/> enables annotation driven transaction management
- PersistenceAnnotationBeanPostProcessor is for exception translation, we don’t extend from JPATemplate but we can still have this feature.
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence 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"
version="1.0">
<persistence-unit name="moviestore" transaction-type="RESOURCE_LOCAL">
<class>com.prime.tutorial.moviestore.domain.Movie</class>
</persistence-unit>
</persistence>
faces-config.xml - Pretty empty:)
<?xml version="1.0" encoding="utf-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
version="1.2">
<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
<variable-resolver>org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver>
</application>
</faces-config>
- DelegatingVariableResolver helps JSF to resolve spring managed beans like CreateMovie.
Finally the web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
<filter>
<filter-name>JPA Filter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>JPA Filter</filter-name>
<url-pattern>*.jsf</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
</web-app>
- org.springframework.web.context.request.RequestContextListener listener enables spring scopes with @Scope annotation
- JPA Filter is same as OpenSessionInViewFilter but for jpa.
Testing
I’ve also included an integration test for MovieDAOWithJPA, it does not subclass AbstractJPATests and configured completely on spring’s annotation support for testing.
package com.prime.tutorial.moviestore.dao;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
import com.prime.tutorial.moviestore.domain.Movie;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
@TransactionConfiguration(transactionManager="txManager")
@ContextConfiguration(locations={"/applicationContext.xml"})
public class MovieDAOWithJPATest{
private MovieDAO movieDAO;
@Autowired
public void setRepository(MovieDAO movieDAO) {
this.movieDAO = movieDAO;
}
@Test
public void shouldPersistNewMovie() {
Movie movie = new Movie();
movie.setTitle("Scarface");
movie.setDiscs(new Integer(2));
movieDAO.persist(movie);
assertNotNull(movie.getId());
}
@Test
public void shouldFindByTitle() {
Movie movie = new Movie();
movie.setTitle("Carlito's Way");
movie.setDiscs(new Integer(1));
movieDAO.persist(movie);
assertNotNull(movie.getId());
List results = movieDAO.findByTitle("Carlito's Way");
assertEquals(1, results.size());
assertEquals("Carlito's Way", results.get(0).getTitle());
}
@Test
public void shouldReadAllMovies() {
Movie movie1 = new Movie();
movie1.setTitle("Godfather Part I");
movie1.setDiscs(new Integer(1));
Movie movie2 = new Movie();
movie2.setTitle("Godfather Part II");
movie2.setDiscs(new Integer(1));
movieDAO.persist(movie1);
assertNotNull(movie1.getId());
movieDAO.persist(movie2);
assertNotNull(movie2.getId());
List results = movieDAO.loadAll();
assertEquals(2, results.size());
}
}
I’ve packaged my moviestore example and uploaded to http://people.apache.org/~cagatay/moviestore.rar, build is based on maven2 and you can easily get it running with;
mvn jetty:run
And then go to -> http://localhost:8080/moviestore
The database is hsqldb with inmemory setting.Final words;
I’ve actually created this sample app to be used in my consulting work but if you’ve decided to go with JSF-Spring-JPA, it is definitely a good resource for kickstart.