Création d’une application CRUD avec JSF2, Spring, Hibernate, Primefaces et Pretty Faces (partie 1)

 

En tant que Développeur JAVA JEE, j’ai l’habitude de fréquenter les forums techniques. J’ai noté une question récurrente : “Comment intégrer JSF2 avec Spring et Hibernate ?”

Dans ce contexte, et afin de nous détacher des exemples de type “HelloWorld”, je vous propose un tutoriel qui vous permettra de créer une application CRUD utilisant à la fois :

  • Spring pour l’injection de dépendances
  • Hibernate pour le mapping
  • Primefaces pour les jeux de composants
  • PrettyFaces pour la réécriture des URL.

ENVIRONNEMENT DE DEVELOPPEMENT

Tout au long de ce tutoriel, nous utiliserons:
Eclipse JUNO,  jdk-7u13,  Maven 3,  JSF 2.1.4,  Spring ,  Primefaces,  PrettyFaces, Junit, Mockito.

LIBRAIRIE

Commençons par créer un projet Maven et ajoutons toutes ces dépendances.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.paloit</groupId>
	<artifactId>myApp</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>MyApp</name>
	<description>MyApp</description>

	<properties>
		<!-- Build -->
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.6</java.version>
		<wtp.version>2.0</wtp.version>

		<!-- Test -->
		<junit.version>4.10</junit.version>

		<!-- SPRING -->

		<spring.version>3.2.0.RELEASE</spring.version>

		<!-- HIBERNATE -->

		<hibernate.version>4.1.9.Final</hibernate.version>

		<!-- Servlet et pages -->
		<mojarra.version>2.1.4</mojarra.version>
		<jstl.version>1.2</jstl.version>
		<servlet-api.version>2.5</servlet-api.version>

		<!-- Plugins -->
		<maven-compiler-plugin.version>2.3.2</maven-compiler-plugin.version>
		<maven-eclipse-plugin.version>2.8</maven-eclipse-plugin.version>
		<tomcat-maven-plugin.version>1.1</tomcat-maven-plugin.version>

	</properties>

	<dependencies>

		<!-- JSF -->
		<dependency>
			<groupId>com.sun.faces</groupId>
			<artifactId>jsf-api</artifactId>
			<version>${mojarra.version}</version>
		</dependency>

		<dependency>
			<groupId>com.sun.faces</groupId>
			<artifactId>jsf-impl</artifactId>
			<version>${mojarra.version}</version>
			<scope>compile</scope>
		</dependency>

		<!-- PRETTY FACES -->

		<dependency>
			<groupId>com.ocpsoft</groupId>
			<artifactId>prettyfaces-jsf2</artifactId>
			<version>3.3.2</version>
		</dependency>

		<!-- JSTL -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>${jstl.version}</version>
		</dependency>

		<!-- Servlet 2.5 -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>${servlet-api.version}</version>
			<scope>provided</scope>
		</dependency>

		<!-- SPRING -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<!-- HIBERNATE -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.version}</version>
		</dependency>

		<!-- JUNIT -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.2.9</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>4.2.0.CR1</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-c3p0</artifactId>
			<version>4.2.0.CR1</version>
		</dependency>
		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>20030203.000129</version>
		</dependency>
		<dependency>
			<groupId>org.mockito</groupId>
			<artifactId>mockito-all</artifactId>
			<version>1.9.5</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>myApp</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-eclipse-plugin</artifactId>
				<version>${maven-eclipse-plugin.version}</version>
				<configuration>
					<wtpversion>${wtp.version}</wtpversion>
					<source>${java.version}</source>
					<target>${java.version}</target>
					<downloadSources>true</downloadSources>
					<downloadJavadocs>true</downloadJavadocs>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>


PASSONS AUX CHOSES SERIEUSES !

Avant tout, nous allons créer une Entité Personne qui nous servira d’exemple tout au long de ce tutoriel.

package com.paloit.entities;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.hibernate.annotations.GenericGenerator;

@Entity
@Table(name = "person")
public class Person implements Serializable {
	/**
	 *
	 */
	private static final long serialVersionUID = 8496087166198616020L;
	private String id;
	private String name;
	private Integer age;

	@Id
	@GeneratedValue(generator = "system-uuid")
	@GenericGenerator(name = "system-uuid", strategy = "uuid")
	@Column(name = "PersonId")
	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	@Column(name = "PersonName", nullable = false)
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Column(name = "PersonAge")
	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Person == false) {
			return false;
		}
		if (this == obj) {
			return true;
		}
		final Person person = (Person) obj;
		return new EqualsBuilder().append(name, person.getName())
				.append(id, person.getId()).append(age, person.getAge())
				.isEquals();
	}

}

Comme vous le voyez, nous avons utilisé une approche de génération des IDs. Ainsi, lors de l’enregistrement, les IDs seront automatiquement générés.

Une fois notre entité créée, nous passerons à la configuration de la connexion de la base de données. Ici, nous utiliserons une base de données mémoire (HSQL) et Spring database-embeded, qui est une base de données embarquée facile à configurer.

db-config.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:p="http://www.springframework.org/schema/p"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc" 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.xsd
	http://www.springframework.org/schema/tx
	http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
	http://www.springframework.org/schema/jdbc
	http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.0.xsd">
	<jdbc:embedded-database id="dataSource" type="HSQL">
<!-- 		<jdbc:script location="schema.sql"/> -->
<!-- 		<jdbc:script location="data.sql"/> -->
	</jdbc:embedded-database>
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
		p:dataSource-ref="dataSource" p:packagesToScan="com.paloit.entities">
		<property name="hibernateProperties">
            <value>
                hibernate.format_sql=true
                hibernate.dialect=org.hibernate.dialect.HSQLDialect
                hibernate.hbm2ddl.auto=create
            </value>
        </property>
	</bean>
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate4.HibernateTransactionManager">
		<property name="sessionFactory">
			<ref bean="sessionFactory" />
		</property>
	</bean>
	<tx:annotation-driven />
</beans>

Avant de passer à la partie DAO, faisons des tests unitaires afin de nous assurer que notre configuration est correcte. Créons la classe PersonTest.java :

package com.paloit.dao;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.AfterClass;
import org.junit.Assert;
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 com.paloit.entities.Person;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "/db-config.xml" })
public class PersonTest {
	static Session  session;

	@Autowired
	public void setFactory(SessionFactory factory) {
		session = factory.openSession();
	}

	@Test
	public void testConfiguration() {
		// Setup
		Person person = new Person();
		person.setName("Palo");
		person.setAge(21);
		session.beginTransaction();
		// Action
		person=(Person) session.merge(person);
		List persons = session.createCriteria(Person.class).list();
		// Test
		Assert.assertEquals(1, persons.size());
		Assert.assertEquals(person, persons.get(0));
	}

	@AfterClass
	public static void tearDown(){
		session.close();
	}

}

testperson

Dans la plupart des tutoriels que j’ai consultés, les tests se font seulement au niveau des DAO. Je pense que cela ressemble plus à un test d’intégration qu’un test unitaire. En effet, dès lors qu’on considère que la partie mapping et la partie DAO sont deux couches différentes, on doit les tester l’une après l’autre afin de s’assurer qu’elles peuvent fonctionner séparément.

Une fois la partie mapping testée, nous allons maintenant créer la classe DAO.  Cette classe sera très simple car elle comporte seulement les méthodes CRUD (Create, Read, Update and Delete).


package com.paloit.dao;

import java.util.List;

import com.paloit.entities.Person;

public interface PersonDao {
	public void savePerson(Person person);

	public List getAllPersons();

	public Person getPersonById(String id);

	public void deletePerson(Person person);

	public List getPersonbyName(String name);

}

 

package com.paloit.dao;

import java.util.List;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.paloit.entities.Person;

@Repository("personDao")
public class PersonDaoImpl implements PersonDao{

	private SessionFactory sessionFactory;

	@Autowired
	public void setSessionFactory(SessionFactory factory){
		sessionFactory=factory;
	}

	public void savePerson(Person person) {
		sessionFactory.getCurrentSession().merge(person);
	}

	@SuppressWarnings("unchecked")
	public List getAllPersons() {
		return sessionFactory.getCurrentSession().createCriteria(Person.class).list();
	}

	public Person getPersonById(String id) {
		return (Person) sessionFactory.getCurrentSession().get(Person.class, id);
	}

	public void deletePerson(Person person) {
		sessionFactory.getCurrentSession().delete(person);
	}

	public List getPersonbyName(String name) {
		return sessionFactory.getCurrentSession().createQuery("From Person WHERE name =:name").list();
	}

}

Passons maintenant au test du DAO. Pour cela, nous allons utiliser un framework de Mock pour simuler les appels de certains objets de la couche DAO. En effet, l’un des problèmes majeurs des tests unitaires sont les objets utilisés. Certains sont difficiles à instancier alors que d’autres ont un comportement difficilement contrôlable. C’est là qu’interviennent les frameworks de Mock. Ces frameworks permettent de simuler l’appel des méthodes et de les contrôler. Dans ce tutoriel, nous allons utiliser Mockito.

package com.paloit.dao;

import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import com.paloit.entities.Person;

public class PersonDaoTest {
	static PersonDaoImpl dao;
	static SessionFactory factory;
	static Session session;

	@BeforeClass
	public static void initClass(){
		dao = new PersonDaoImpl();
		factory = mock(SessionFactory.class);
		session = mock(Session.class);
		dao.setSessionFactory(factory);
	}

	@Before
	public void init() {
		reset(factory,session);
		when(factory.getCurrentSession()).thenReturn(session);
	}

	@Test(expected = HibernateException.class)
	public void testCreateDataKO() {
		// Setup
		Person user = new Person();
		when(session.merge(user)).thenThrow(new HibernateException("fail"));
		// Action
		dao.savePerson(user);
	}

	@Test
	public void testCreateDataOK() {
		// Setup
		Person input = new Person();
		// Action
		dao.savePerson(input);
		// test
		verify(factory, times(1)).getCurrentSession();
		verify(session, times(1)).merge(input);
	}

	@Test(expected = HibernateException.class)
	public void testRetrieveDataKO() {
		// /Setup
		Criteria criteria = mock(Criteria.class);
		when(session.createCriteria(Person.class)).thenReturn(criteria);
		when(criteria.list()).thenThrow(new HibernateException(""));
		dao.getAllPersons();
	}

	@Test
	public void testRetrieveDataOK() {
		// /Setup
		Criteria criteria = mock(Criteria.class);
		when(session.createCriteria(Person.class)).thenReturn(criteria);
		List persons = mock(List.class);
		when(criteria.list()).thenReturn(persons);
		// Action
		dao.getAllPersons();
		// Test
		verify(session, times(1)).createCriteria(Person.class);
		verify(criteria, times(1)).list();
	}

	@Test(expected = HibernateException.class)
	public void testDeleteDataKO() {
		// Setup
		Person input = new Person();
		doThrow(new HibernateException("")).when(session).delete(input);
		// Action
		dao.deletePerson(input);
	}

	@Test
	public void testDeleteDataOK() {
		// Setup
		Person input = new Person();
		// Action
		dao.deletePerson(input);
		// Test
		verify(factory, times(1)).getCurrentSession();
		verify(session, times(1)).delete(input);
	}

}

testpersondao

Passons maintenant à la configuration Spring. Nous allons ainsi créer un fichier XML qu’on appellera applicationContext.xml. Dans ce fichier, nous ferons un scan des packages contenant les annotations et importerons le fichier db-config.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"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<import resource="db-config.xml" />

<context:component-scan base-package="com.paloit" />

</beans>

Nous allons créer les classes de la couche service. Celles-ci appellent la classe PersonDao. Nous allons l’injecter via Spring :

package com.paloit.manager;

import java.util.List;

import com.paloit.entities.Person;

public interface PersonManager {

	public void savePerson(Person person);

	public List getAllPersons();

	public Person getPersonById(String id);

	public void deletePerson(Person person);

}

 

package com.paloit.manager;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import com.paloit.dao.PersonDao;
import com.paloit.entities.Person;

@Service
@Transactional(readOnly = true)
public class PersonManagerImpl implements PersonManager {

	private PersonDao personDao;

	@Autowired
	public void setPersonDao(PersonDao personDao) {
		this.personDao = personDao;
	}

	@Transactional(readOnly = false)
	public void savePerson(Person person) {
		personDao.savePerson(person);

	}

	public List getAllPersons() {
		return personDao.getAllPersons();
	}

	public Person getPersonById(String id) {
		return personDao.getPersonById(id);
	}

	@Transactional(readOnly = false)
	public void deletePerson(Person person) {
		personDao.deletePerson(person);
	}

}

Maintenant, passons à la classe de Test. Comme dans la classe DAO, on utilisera Mockito.

package com.paloit.manager;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.hibernate.HibernateException;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import com.paloit.dao.PersonDao;
import com.paloit.entities.Person;

public class PersonManagerImplTest {

	private static PersonManagerImpl managerImpl;
	private static PersonDao personDao;

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		managerImpl = new PersonManagerImpl();
		personDao = mock(PersonDao.class);
		managerImpl.setPersonDao(personDao);
	}

	@Before
	public void setUp() throws Exception {
		reset(personDao);
	}

	@Test(expected = HibernateException.class)
	public void testSavePersonKO() {
		// Setup
		Person person = new Person();
		doThrow(new HibernateException("fail")).when(personDao).savePerson(
				person);
		// Action
		managerImpl.savePerson(person);
	}

	@Test
	public void testSavePersonOK() {
		// Setup
		Person person = new Person();
		// Action
		managerImpl.savePerson(person);
		// Test
		verify(personDao, times(1)).savePerson(person);
	}

	@Test(expected = HibernateException.class)
	public void testGetPersonByIdKO() {
		// Setup
		when(personDao.getPersonById("id")).thenThrow(
				new HibernateException("fail"));
		// Action
		managerImpl.getPersonById("id");
	}

	@Test
	public void testGetPersonByIdOK() {
		// Setup
		Person input = new Person();
		String id = "id";
		input.setAge(23);
		input.setId(id);
		input.setName("palo");
		when(personDao.getPersonById(id)).thenReturn(input);
		// Action
		Person output = managerImpl.getPersonById(id);
		// Test
		assertEquals(input, output);
		verify(personDao, times(1)).getPersonById(id);
	}

}

testpersonservice
Je n’ai pas testé toutes les méthodes : je vous laisse le soin d’effectuer le test des autres ! Nous aborderons la suite dans la deuxième partie de notre tutoriel, soit la partie graphique avec JSF et Primefaces, ainsi que la gestion des URL avec Pretty Faces.

Découvrir la partie 2

Share
Oumar CISSE
Oumar CISSE

5734

Comments

  1. […] partie du tutoriel, j’avais déjà mis en place la partie coeur de l’application (voir ici). Dans cette partie, nous allons  aborder son aspect graphique avec la mise en place de JSF2, […]

  2. patrickpasset Says: February 26, 2013 at 12:26 pm

    J’aime bien votre blog mais est ce qu’il vous est possible de poster les articles de maniere à ce que il y ai un petit résumé de 300 lignes puis on clicke sur more pour voir l’article en entier parce que ça rend votre homepage imbvable si non!

  3. Emeline MOY Emeline MOY Says: February 26, 2013 at 2:13 pm

    Sur chaque article, il y a une balise “More” mais en effet, celle-ci a été omise à certains endroits. Nous allons rectifier le tir ! Merci de votre suivi.

  4. abdou rabih Says: March 5, 2013 at 12:16 pm

    bonjour,
    j’ai une base de donnés assez grande et je veux générer directement les classes de la base de données, n’existe pas une possibilité avec eclipse et hibernate pour les générer (sans se casser la tete avec les annotations) comme dans le cas de netbeans
    merci d’avance

  5. abdou rabih Says: March 5, 2013 at 1:06 pm

    une autre question : en relisant votre tuto et un autre (http://www.onlinetechvision.com/?p=566): j’ai conclu que pour chaque table de la base données je dois avoir 2 interfaces (une dao et une service ) et 2 classes implementant ces interfaces et un bean
    et dans mon cas il y a une grande base de données : n’existe pas une astuce ou une alternative pour eviter ce grand nombre

    merci d’avance

  6. Oumar CISSE Oumar CISSE Says: March 5, 2013 at 1:40 pm

    Pour ta première question tu peux utiliser hibernate tools. Il peut à partir d’eclipse de générer tes classes de mapping à partir de la base de données. Et si je ne me trompe pas elle peut créer tes classes DAO.
    Pour ta deuxième question d’habitude ce que je fais je créer une classe générique qui me GeneriqueService qui contient les méthode de base comme add, delete, getList, getById ça ne m’évitera pas de créer autant de classe et d’interfaces mais elle permet de ne pas implémenter les mêmes éthodes partout

Leave a Reply

Your email address will not be published. Required fields are marked *