Tutoriel sur la création d'une application CRUD avec JSF2 (partie 1) : mise en œuvre de Spring et Hibernate

| Oumar Cisse (PaloIT) | PaloIT |

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.

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum Commentez .

- Titre : Tutoriel sur la création d'une application CRUD avec JSF2 (partie 1) : mise en œuvre de Spring et Hibernate

- Auteurs : Oumar Cisse (PaloIT), PaloIT

- Parution : 17 juin 2013

- Licence : Copyright ® 2013 PaloIT. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Environnement de développement

- II. Déclaration des bibliothèques via Maven

- III. Mise en place de la couche entité

- IV. Mise en place de la couche DAO

- V. Mise en place de la couche service

- VI. Conclusion

- VII. Remerciements

I - Environnement de développement

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

II - Déclaration des bibliothèques via Maven

Commençons par créer un projet Maven et ajoutons toutes ses dépendances afin d'intégrer toutes les bibliothèques tierces nécessaires.

pom.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

4. <modelVersion>4.0.0</modelVersion>

5. <groupId>com.paloit</groupId>

6. <artifactId>myApp</artifactId>

7. <packaging>war</packaging>

8. <version>1.0-SNAPSHOT</version>

9. <name>MyApp</name>

10. <description>MyApp</description>

12. <properties>

13. <!-- Build -->

14. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

15. <java.version>1.6</java.version>

16. <wtp.version>2.0</wtp.version>

18. <!-- Test -->

19. <junit.version>4.10</junit.version>

21. <!-- SPRING -->

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

25. <!-- HIBERNATE -->

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

29. <!-- Servlet et pages -->

30. <mojarra.version>2.1.4</mojarra.version>

31. <jstl.version>1.2</jstl.version>

32. <servlet-api.version>2.5</servlet-api.version>

34. <!-- Plugins -->

35. <maven-compiler-plugin.version>2.3.2</maven-compiler-plugin.version>

36. <maven-eclipse-plugin.version>2.8</maven-eclipse-plugin.version>

37. <tomcat-maven-plugin.version>1.1</tomcat-maven-plugin.version>

39. </properties>

41. <dependencies>

43. <!-- JSF -->

44. <dependency>

45. <groupId>com.sun.faces</groupId>

46. <artifactId>jsf-api</artifactId>

47. <version>${mojarra.version}</version>

48. </dependency>

50. <dependency>

51. <groupId>com.sun.faces</groupId>

52. <artifactId>jsf-impl</artifactId>

53. <version>${mojarra.version}</version>

54. <scope>compile</scope>

55. </dependency>

57. <!-- PRETTY FACES -->

59. <dependency>

60. <groupId>com.ocpsoft</groupId>

61. <artifactId>prettyfaces-jsf2</artifactId>

62. <version>3.3.2</version>

63. </dependency>

65. <!-- JSTL -->

66. <dependency>

67. <groupId>javax.servlet</groupId>

68. <artifactId>jstl</artifactId>

69. <version>${jstl.version}</version>

70. </dependency>

72. <!-- Servlet 2.5 -->

73. <dependency>

74. <groupId>javax.servlet</groupId>

75. <artifactId>servlet-api</artifactId>

76. <version>${servlet-api.version}</version>

77. <scope>provided</scope>

78. </dependency>

80. <!-- SPRING -->

81. <dependency>

82. <groupId>org.springframework</groupId>

83. <artifactId>spring-orm</artifactId>

84. <version>${spring.version}</version>

85. </dependency>

86. <dependency>

87. <groupId>org.springframework</groupId>

88. <artifactId>spring-test</artifactId>

89. <version>${spring.version}</version>

90. </dependency>

91. <!-- HIBERNATE -->

92. <dependency>

93. <groupId>org.hibernate</groupId>

94. <artifactId>hibernate-entitymanager</artifactId>

95. <version>${hibernate.version}</version>

96. </dependency>

98. <!-- JUNIT -->

99. <dependency>

100. <groupId>junit</groupId>

101. <artifactId>junit</artifactId>

102. <version>${junit.version}</version>

103. <scope>test</scope>

104. </dependency>

106. <dependency>

107. <groupId>org.hsqldb</groupId>

108. <artifactId>hsqldb</artifactId>

109. <version>2.2.9</version>

110. </dependency>

111. <dependency>

112. <groupId>org.hibernate</groupId>

113. <artifactId>hibernate-core</artifactId>

114. <version>4.2.0.CR1</version>

115. </dependency>

116. <dependency>

117. <groupId>org.hibernate</groupId>

118. <artifactId>hibernate-c3p0</artifactId>

119. <version>4.2.0.CR1</version>

120. </dependency>

121. <dependency>

122. <groupId>commons-lang</groupId>

123. <artifactId>commons-lang</artifactId>

124. <version>20030203.000129</version>

125. </dependency>

126. <dependency>

127. <groupId>org.mockito</groupId>

128. <artifactId>mockito-all</artifactId>

129. <version>1.9.5</version>

130. </dependency>

131. </dependencies>

132. <build>

133. <finalName>myApp</finalName>

134. <plugins>

135. <plugin>

136. <groupId>org.apache.maven.plugins</groupId>

137. <artifactId>maven-eclipse-plugin</artifactId>

138. <version>${maven-eclipse-plugin.version}</version>

139. <configuration>

140. <wtpversion>${wtp.version}</wtpversion>

141. <source>${java.version}</source>

142. <target>${java.version}</target>

143. <downloadSources>true</downloadSources>

144. <downloadJavadocs>true</downloadJavadocs>

145. </configuration>

146. </plugin>

147. </plugins>

148. </build>

150. </project>

III - Mise en place de la couche entité

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

Person.java

1. package com.paloit.entities;

3. import java.io.Serializable;

5. import javax.persistence.Column;

6. import javax.persistence.Entity;

7. import javax.persistence.GeneratedValue;

8. import javax.persistence.Id;

9. import javax.persistence.Table;

11. import org.apache.commons.lang.builder.EqualsBuilder;

12. import org.hibernate.annotations.GenericGenerator;

14. @Entity

15. @Table(name = "person")

16. public class Person implements Serializable {

17. /**

18. *

19. */

20. private static final long serialVersionUID = 8496087166198616020L;

21. private String id;

22. private String name;

23. private Integer age;

25. @Id

26. @GeneratedValue(generator = "system-uuid")

27. @GenericGenerator(name = "system-uuid", strategy = "uuid")

28. @Column(name = "PersonId")

29. public String getId() {

30. return id;

31. }

33. public void setId(String id) {

34. this.id = id;

35. }

37. @Column(name = "PersonName", nullable = false)

38. public String getName() {

39. return name;

40. }

42. public void setName(String name) {

43. this.name = name;

44. }

46. @Column(name = "PersonAge")

47. public Integer getAge() {

48. return age;

49. }

51. public void setAge(Integer age) {

52. this.age = age;

53. }

55. @Override

56. public boolean equals(Object obj) {

57. if (obj instanceof Person == false) {

58. return false;

59. }

60. if (this == obj) {

61. return true;

62. }

63. final Person person = (Person) obj;

64. return new EqualsBuilder().append(name, person.getName())

65. .append(id, person.getId()).append(age, person.getAge())

66. .isEquals();

67. }

69. }

Comme vous le voyez, nous avons utilisé une approche de génération des identifiants (ID). Ainsi, lors de l'enregistrement, les identifiants 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

1. <?xml version="1.0" encoding="UTF-8"?>

2. <beans xmlns="http://www.springframework.org/schema/beans"

3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"

4. xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context"

5. xmlns:tx="http://www.springframework.org/schema/tx"

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

7. http://www.springframework.org/schema/tx

8. http://www.springframework.org/schema/tx/spring-tx-3.0.xsd

9. http://www.springframework.org/schema/jdbc

10. http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd

11. http://www.springframework.org/schema/context

12. http://www.springframework.org/schema/context/spring-context-3.0.xsd">

14. <jdbc:embedded-database id="dataSource" type="HSQL">

15. <!--         <jdbc:script location="schema.sql"/> -->

16. <!--         <jdbc:script location="data.sql"/> -->

17. </jdbc:embedded-database>

18. <bean id="sessionFactory"

19. class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"

20. p:dataSource-ref="dataSource" p:packagesToScan="com.paloit.entities">

21. <property name="hibernateProperties">

22. <value>

23. hibernate.format_sql=true

24. hibernate.dialect=org.hibernate.dialect.HSQLDialect

25. hibernate.hbm2ddl.auto=create

26. </value>

27. </property>

28. </bean>

29. <bean id="transactionManager"

30. class="org.springframework.orm.hibernate4.HibernateTransactionManager">

31. <property name="sessionFactory">

32. <ref bean="sessionFactory" />

33. </property>

34. </bean>

35. <tx:annotation-driven />

36. </beans>

IV - Mise en place de la couche DAO

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 :

PersonTest.java

1. package com.paloit.dao;

3. import java.util.List;

5. import org.hibernate.Session;

6. import org.hibernate.SessionFactory;

7. import org.junit.AfterClass;

8. import org.junit.Assert;

9. import org.junit.Test;

10. import org.junit.runner.RunWith;

11. import org.springframework.beans.factory.annotation.Autowired;

12. import org.springframework.test.context.ContextConfiguration;

13. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

15. import com.paloit.entities.Person;

17. @RunWith(SpringJUnit4ClassRunner.class)

18. @ContextConfiguration({ "/db-config.xml" })

19. public class PersonTest {

20. static Session  session;

22. @Autowired

23. public void setFactory(SessionFactory factory) {

24. session = factory.openSession();

25. }

27. @Test

28. public void testConfiguration() {

29. // Setup

30. Person person = new Person();

31. person.setName("Palo");

32. person.setAge(21);

33. session.beginTransaction();

34. // Action

35. person=(Person) session.merge(person);

36. List persons = session.createCriteria(Person.class).list();

37. // Test

38. Assert.assertEquals(1, persons.size());

39. Assert.assertEquals(person, persons.get(0));

40. }

42. @AfterClass

43. public static void tearDown(){

44. session.close();

45. }

46. }

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

PersonDao

1. package com.paloit.dao;

3. import java.util.List;

5. import com.paloit.entities.Person;

7. public interface PersonDao {

8. void savePerson(Person person);

10. List getAllPersons();

12. Person getPersonById(String id);

14. void deletePerson(Person person);

16. List getPersonbyName(String name);

18. }

PersonDaoImpl

1. package com.paloit.dao;

3. import java.util.List;

5. import org.hibernate.SessionFactory;

6. import org.springframework.beans.factory.annotation.Autowired;

7. import org.springframework.stereotype.Repository;

9. import com.paloit.entities.Person;

11. @Repository("personDao")

12. public class PersonDaoImpl implements PersonDao{

14. private SessionFactory sessionFactory;

16. @Autowired

17. public void setSessionFactory(SessionFactory factory){

18. sessionFactory=factory;

19. }

21. public void savePerson(Person person) {

22. sessionFactory.getCurrentSession().merge(person);

23. }

25. @SuppressWarnings("unchecked")

26. public List getAllPersons() {

27. return sessionFactory.getCurrentSession().createCriteria(Person.class).list();

28. }

30. public Person getPersonById(String id) {

31. return (Person) sessionFactory.getCurrentSession().get(Person.class, id);

32. }

34. public void deletePerson(Person person) {

35. sessionFactory.getCurrentSession().delete(person);

36. }

38. public List getPersonbyName(String name) {

39. return sessionFactory.getCurrentSession().createQuery("From Person WHERE name =:name").list();

40. }

42. }

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, les objets utilisés sont l'un des problèmes majeurs des tests unitaires . 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.

PersonDaoTest

1. package com.paloit.dao;

3. import static org.mockito.Mockito.doThrow;

4. import static org.mockito.Mockito.mock;

5. import static org.mockito.Mockito.reset;

6. import static org.mockito.Mockito.times;

7. import static org.mockito.Mockito.verify;

8. import static org.mockito.Mockito.when;

10. import java.util.List;

12. import org.hibernate.Criteria;

13. import org.hibernate.HibernateException;

14. import org.hibernate.Session;

15. import org.hibernate.SessionFactory;

16. import org.junit.Before;

17. import org.junit.BeforeClass;

18. import org.junit.Test;

20. import com.paloit.entities.Person;

22. public class PersonDaoTest {

23. static PersonDaoImpl dao;

24. static SessionFactory factory;

25. static Session session;

27. @BeforeClass

28. public static void initClass(){

29. dao = new PersonDaoImpl();

30. factory = mock(SessionFactory.class);

31. session = mock(Session.class);

32. dao.setSessionFactory(factory);

33. }

35. @Before

36. public void init() {

37. reset(factory,session);

38. when(factory.getCurrentSession()).thenReturn(session);

39. }

41. @Test(expected = HibernateException.class)

42. public void testCreateDataKO() {

43. // Setup

44. Person user = new Person();

45. when(session.merge(user)).thenThrow(new HibernateException("fail"));

46. // Action

47. dao.savePerson(user);

48. }

50. @Test

51. public void testCreateDataOK() {

52. // Setup

53. Person input = new Person();

54. // Action

55. dao.savePerson(input);

56. // test

57. verify(factory, times(1)).getCurrentSession();

58. verify(session, times(1)).merge(input);

59. }

61. @Test(expected = HibernateException.class)

62. public void testRetrieveDataKO() {

63. // /Setup

64. Criteria criteria = mock(Criteria.class);

65. when(session.createCriteria(Person.class)).thenReturn(criteria);

66. when(criteria.list()).thenThrow(new HibernateException(""));

67. dao.getAllPersons();

68. }

70. @Test

71. public void testRetrieveDataOK() {

72. // /Setup

73. Criteria criteria = mock(Criteria.class);

74. when(session.createCriteria(Person.class)).thenReturn(criteria);

75. List persons = mock(List.class);

76. when(criteria.list()).thenReturn(persons);

77. // Action

78. dao.getAllPersons();

79. // Test

80. verify(session, times(1)).createCriteria(Person.class);

81. verify(criteria, times(1)).list();

82. }

84. @Test(expected = HibernateException.class)

85. public void testDeleteDataKO() {

86. // Setup

87. Person input = new Person();

88. doThrow(new HibernateException("")).when(session).delete(input);

89. // Action

90. dao.deletePerson(input);

91. }

93. @Test

94. public void testDeleteDataOK() {

95. // Setup

96. Person input = new Person();

97. // Action

98. dao.deletePerson(input);

99. // Test

100. verify(factory, times(1)).getCurrentSession();

101. verify(session, times(1)).delete(input);

102. }

104. }

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.

applicationContext.xml

1. <?xml version="1.0" encoding="UTF-8"?>

3. <beans xmlns="http://www.springframework.org/schema/beans"

4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

5. xmlns:context="http://www.springframework.org/schema/context"

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

8. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

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

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

14. </beans>

V - Mise en place de la couche service

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

PersonManager

1. package com.paloit.manager;

3. import java.util.List;

5. import com.paloit.entities.Person;

7. public interface PersonManager {

9. void savePerson(Person person);

11. List getAllPersons();

13. Person getPersonById(String id);

15. void deletePerson(Person person);

17. }

PersonManagerImpl

1. package com.paloit.manager;

3. import java.util.List;

5. import org.springframework.beans.factory.annotation.Autowired;

6. import org.springframework.transaction.annotation.Transactional;

8. import com.paloit.dao.PersonDao;

9. import com.paloit.entities.Person;

11. @Service

12. @Transactional(readOnly = true)

13. public class PersonManagerImpl implements PersonManager {

15. private PersonDao personDao;

17. @Autowired

18. public void setPersonDao(PersonDao personDao) {

19. this.personDao = personDao;

20. }

22. @Transactional(readOnly = false)

23. public void savePerson(Person person) {

24. personDao.savePerson(person);

26. }

28. public List getAllPersons() {

29. return personDao.getAllPersons();

30. }

32. public Person getPersonById(String id) {

33. return personDao.getPersonById(id);

34. }

36. @Transactional(readOnly = false)

37. public void deletePerson(Person person) {

38. personDao.deletePerson(person);

39. }

41. }

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

PersonManagerImplTest

1. package com.paloit.manager;

3. import static org.junit.Assert.assertEquals;

4. import static org.mockito.Mockito.doThrow;

5. import static org.mockito.Mockito.mock;

6. import static org.mockito.Mockito.reset;

7. import static org.mockito.Mockito.times;

8. import static org.mockito.Mockito.verify;

9. import static org.mockito.Mockito.when;

11. import org.hibernate.HibernateException;

12. import org.junit.Before;

13. import org.junit.BeforeClass;

14. import org.junit.Test;

16. import com.paloit.dao.PersonDao;

17. import com.paloit.entities.Person;

19. public class PersonManagerImplTest {

21. private static PersonManagerImpl managerImpl;

22. private static PersonDao personDao;

24. @BeforeClass

25. public static void setUpBeforeClass() throws Exception {

26. managerImpl = new PersonManagerImpl();

27. personDao = mock(PersonDao.class);

28. managerImpl.setPersonDao(personDao);

29. }

31. @Before

32. public void setUp() throws Exception {

33. reset(personDao);

34. }

36. @Test(expected = HibernateException.class)

37. public void testSavePersonKO() {

38. // Setup

39. Person person = new Person();

40. doThrow(new HibernateException("fail")).when(personDao).savePerson(

41. person);

42. // Action

43. managerImpl.savePerson(person);

44. }

46. @Test

47. public void testSavePersonOK() {

48. // Setup

49. Person person = new Person();

50. // Action

51. managerImpl.savePerson(person);

52. // Test

53. verify(personDao, times(1)).savePerson(person);

54. }

56. @Test(expected = HibernateException.class)

57. public void testGetPersonByIdKO() {

58. // Setup

59. when(personDao.getPersonById("id")).thenThrow(

60. new HibernateException("fail"));

61. // Action

62. managerImpl.getPersonById("id");

63. }

65. @Test

66. public void testGetPersonByIdOK() {

67. // Setup

68. Person input = new Person();

69. String id = "id";

70. input.setAge(23);

71. input.setId(id);

72. input.setName("palo");

73. when(personDao.getPersonById(id)).thenReturn(input);

74. // Action

75. Person output = managerImpl.getPersonById(id);

76. // Test

77. assertEquals(input, output);

78. verify(personDao, times(1)).getPersonById(id);

79. }

81. }

VI - Conclusion

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.

VII - Remerciements

Cet article a été publié avec l'aimable autorisation de la société PaloIT.

Nous tenons à remercier ClaudeLELOUP pour sa relecture orthographique attentive de cet article et Mickael Baron pour la mise au gabarit.