Note: below description uses Eclipse Indigo, Tomcat 7.0.28, MyFaces 2.1.7, Spring Framework 3.1.1, Spring Security 3.1.0, Hibernate 3.6.10 (Final), PostgreSQL 9.1.
Requirements:
Requirements:
- running database and complete project with all required libraries integrated, described in previous post (can be found here)
Step 4 (continued): configuration files - faces-config.xml.
Comparing to a JSF 2.0 project with Spring integrated, only applicationContext.xml file will be changed - web.xml and faces-config.xml files will be the same. We need to add below code into applicationContext.xml:
<bean id="pooledDataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="org.postgresql.Driver" />
<property name="jdbcUrl" value="jdbc:postgresql://localhost:5432/bikes"/>
<property name="user" value="postgres"/>
<property name="password" value="pgpass"/>
<property name="maxPoolSize" value="10" />
<property name="maxStatements" value="0" />
<property name="minPoolSize" value="5" />
</bean>
<bean id="JDBCDataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name="driverClassName" value="org.postgresql.Driver"/>
<property name="url" value="jdbc:postgresql://localhost:5432/bikes"/>
<property name="username" value="postgres"/>
<property name="password" value="pgpass"/>
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" >
<!--
Specific properties for Hibernate are in persistence.xml file,
but also can be placed here and removed from persistence.xml file.
-->
</bean>
</property>
<property name="dataSource" ref="pooledDataSource" />
<property name="persistenceUnitName" value="persistenceUnit"/>
</bean>
<bean
class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean name="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven />
Note that this approach is similar to situation when plain Hibernate API is used with Spring: JPA's EntityManagerFactory is like Hibernate's SessionFactory, JPA's EntityManager (obtained from EntityManagerFactory) is like Hibernate's Session (obtained from SessionFactory). JPA's EntityManager is injected into Repositories (with the help of Spring's PersistenceAnnotationBeanPostProcessor class), while Hibernate's Session was injected into DAO's (with the help of Spring's HibernateDaoSupport class exteneded by each DAO). JPA's TransactionManager (built on EntityManagerFactory) is like Hibernate's HibernateTransactionManager (built on SessionFactory).
JPA's Repositories and Services are similar to oldschool DAOs and Facades. Repositories are injected into Services, while DAOs are injected into Facades.
Please note that I defined here two datasources: a basic one without connection pool (JDBCDataSource) and second one using c3p0 as a connection pool (pooledDataSource).
Step 5: configuration files - persistence.xml
In addition JPA uses a special configuration file named persistence.xml. This file contains additional settings for JPA vendor. The location of this file is described in this article. In order to have this file inside WEB-INF/classes/META-INF/persistence.xml under Eclipse, I created a directory "config" where I put META-INF subdirectory with persistence.xml file inside. Then I select the directory "config" to be used as a source directory. This means that content of "config" directory will be on classpath, and for a web application during a build time it will be moved into WEB-INF/classes. Note that I also added there log4j config file:
Persistence.xml file contains some specific settings for Hibernate (as JPA vendor):
<?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="persistenceUnit" transaction-type="RESOURCE_LOCAL">
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
Step 6: configuration files - log4j.properties
As I wrote before we will use Log4j instead of Hibernate's default SLF4j. Lets's log everything we can (we can see how Hibernate opens/closes connections, how connections are taken from the pool, how Spring manages transactions and so on):
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n
log4j.rootLogger=info,A1
log4j.logger.com.mchange.v2=DEBUG
log4j.logger.org.hibernate=DEBUG
log4j.logger.org.springframework=DEBUG
Step 7: database entities.
As shown at the beginning of previous post, we had only one entity represented by the class Bike.java. Instances of Bike.java where created inside Spring service nam
ed bikeDataProvider (BikeDataProviderImp.java). Now we have a database with tables Bike, Bike_Category, Account and Role. For each table we should create one class (note: this is the simplest mapping one table to one class - there are other way to map tables to classes).
Bike entity:
package com.jsfsample.model;
@Entity
@Table(name="bike")
public class Bike implements Serializable {
private Integer id;
private BikeCategory category;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="bike_id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@ManyToOne
@JoinColumn(name="bike_category_id")
public BikeCategory getCategory() {
return category;
}
public void setCategory(BikeCategory category) {
this.category = category;
}
// rest of columns
}
Bike_category entity:
package com.jsfsample.model;
@Entity
@Table(name="bike_category")
public class BikeCategory {
private Integer categoryId;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="bike_category_id")
public Integer getCategoryId() {
return categoryId;
}
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
// rest of columns
}
Account entity:
package com.jsfsample.model;
@Entity
@Table(name="account")
public class Person {
private Integer accountId;
private Role currentRole;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="account_id")
public Integer getAccountId() {
return accountId;
}
public void setAccountId(Integer accountId) {
this.accountId = accountId;
}
@ManyToOne
@JoinColumn(name="role_id")
public Role getCurrentRole() {
return currentRole;
}
public void setCurrentRole(Role currentRole) {
this.currentRole = currentRole;
}
// rest of columns
}
Role entity:
package com.jsfsample.model;
@Entity
@Table(name="role")
public class Role {
private Integer roleId;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="role_id")
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
// rest of columns
}
Note how classes are mapped to tables (using @Table(name="...")) and how fields are mapped to columns (using @Column(name="...")). We also mapped many-to-one relations between tables using @ManyToOne annotation with proper joim column name. Note also how primary keys are generated - we do not use PostgreSQL sequence (GenerationType.SEQUENCE) - PostgreSQL supports SERIAL data type for primary keys, which actually hides sequences (see this link).
Step 8: repositories and services.
Spring repositories are similar to DAO. All database interactions is done insde them. As an example, let's look inside repository resposnible for loading and saving bikes - BikesDAOImpl.java:
package com.jsfsample.repositories.impl;
@Repository("BikesRepository")
public class BikesDAOImpl implements BikesDAO {
private EntityManager em = null;
@PersistenceContext
public void setEntityManager(EntityManager em) {
this.em = em;
}
@Override
public Bike loadSelectedBike(Integer bikeId) {
return em.find(Bike.class, bikeId);
}
// other methods for saving bike and loading bike for given category
}
This repository is used in Spring's service named bikeDataProvider (BikeDataProviderImpl.java):
package com.jsfsample.services.impl;
...
@Service("bikeDataProvider")
public class BikeDataProviderImpl implements BikeDataProvider {
@Resource(name="BikesRepository")
private BikesDAO bikesRepository;
@Resource(name="DictionaryRepository")
private DictionariesDAO dictionaryRepository;
public Bike getBikeById(Integer id){
return bikesRepository.loadSelectedBike(id);
}
@Transactional
public void add(Bike newBike, Integer categoryId) {
BikeCategory categorySelected = dictionaryRepository.loadBikeCategroryById(categoryId);
newBike.setCategory(categorySelected);
bikesRepository.saveBike(newBike);
}
// other methods using repositories
}
Similar solution is used for account (person) repository which is then used in Spring's Security service named userDetailsService (UserDetailsServiceImpl.java). Note how easy is to execute a method within database transaction - just use @Transactional annotation and voila.
That's all about adding JPA into Spring-based JSF2 sample application.
The rest of application code (web pages, JSF managed beans) are the same as in pure Spring example project - we only modified services by providing repositories.
How to test it? After deploying application on the server and starting the server, we have to open a browser and type in URL:
http://localhost:8080/JSF2FeaturesSpringJPA
-------------------------------------------
Download source files:
Eclipse complete sample project is here (with all required libraries). The sample project is a ready to run application which contains JPA (Hibernate), Spring (with Spring Security). You can also download a war file located here (just copy it inside webapps folder in Your Tomcat and start Tomcat with the script startup.bat). Make sure that before doing this You created required database and PostgreSQL server is up and running (You can check access using pgAdmin III).