Skip to content

Commit fc7760a

Browse files
committed
first commit.
1 parent 9d352e8 commit fc7760a

File tree

1 file changed

+272
-0
lines changed

1 file changed

+272
-0
lines changed
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
La préparation [de la version 2 de RESThub](http://pullrequest.org/2011/09/07/resthub-2-preview.html) et l’objectif de remplacer [Hades](http://redmine.synyx.org/) par [Spring-data](http://www.springsource.org/spring-data) nous a emmené à étudier le module spring-data-jpa et ses capacités.
2+
3+
## Présentation
4+
5+
Le projet [Spring-data](http://www.springsource.org/spring-data) est un projet visant à simplifier l’utilisation des bases relationnelles et des bases NO SQL (Graph, Key-Value, Document).
6+
En plus des facilités de manipulation de données offertes par le project, Spring-data supporte le framework [QueryDsl](http://www.querydsl.com/) et ainsi la possibilité de donner [une orientation DDD](http://en.wikipedia.org/wiki/Domain-driven_design) introduit par *Eric Evans* à son travail. Sans rentrant dans les détails, on assiste peut être à la fin de nos modèles métiers anémiques !
7+
8+
## Cas d’utilisation basique
9+
Maintenant on rentre dans le vif du sujet avec un projet exemple montrant les possibilités offertes par Spring-data-jpa.
10+
11+
### 1)Objet domain
12+
@Entity
13+
public class User {
14+
@Id
15+
@GeneratedValue(strategy = GenerationType.AUTO)
16+
private Long id;
17+
18+
@Column(unique = true, nullable = false)
19+
private String username;
20+
21+
@Column(nullable = false)
22+
private Integer age;
23+
24+
//Get et Set
25+
}
26+
27+
Ici, Pojo classique pour ne pas dire "anémique". Aucune référence à spring-data n'est nécessaire.
28+
29+
### 2)Repository
30+
public interface UserRepository extends JpaRepository<User, Long> {
31+
User findByUsername(String username);
32+
33+
List<User> findByUsernameAndAge(String username, Integer age);
34+
35+
Page<User> findByUsernameLike(String username, Pageable pageable);
36+
37+
@Query("SELECT u FROM User u WHERE u.username like ?1")
38+
Page<User> findByUsernameLikeCusom(String username, Pageable pageable);
39+
40+
List<User> findByAgeBetween(Integer min, Integer max);
41+
}
42+
43+
Le travail au niveau du repository se limite à l'écriture de l'interface et c'est Spring-data-jpa qui se charge de faire l'implémentation. Les habitués du framework [Hades](http://redmine.synyx.org/) reconnaitrons sans mal ce mode de fonctionnement.
44+
Pour les autres, plusieurs modes sont disponibles :
45+
46+
* le framework compose automatiquement les requêtes en se basant sur des mots clés (ByXXX, Order, …) (ex : findByUsernameAndAge, ...) [liste de mots clés](http://static.springsource.org/spring-data/data-jpa/docs/1.0.0.RC1/reference/html/#repositories.query-methods.property-expressions)
47+
* l’utilisateur écrit directement la requête (utilisation de @Query) avec la posibilité d'utiliser des paramètres nommés
48+
49+
A savoir, qu’il est possible de gérer les Pages pour les requêtes qui peuvent ramener beaucoup de résultats.
50+
51+
### 3)Configuration Spring
52+
Il faut juste indiquer à Spring-data-jpa le package ou se trouve vos repositories qu'il doit gérer
53+
54+
<jpa:repositoriesbase-package="fr.test.repository" />
55+
56+
### 4)Tests
57+
Maintenant on passe aux tests unitaires de notre "userRepository"
58+
public class UserRepositoryTest {
59+
@Autowired
60+
private UserRepository userRepositoryImpl;
61+
62+
private User userTest1 = newUser("Test1", 16);
63+
private User userTest2 = newUser("Test2", 18);
64+
private User userTest3 = newUser("Toto", 21);
65+
private List<User> usersTest = new ArrayList<User>(Arrays.asList(userTest1, userTest2, userTest3));
66+
67+
@Test
68+
public void testSave() {
69+
userRepositoryImpl.save(userTest1);
70+
assertNotNull(userRepositoryImpl.findOne(userTest1.getId()));
71+
}
72+
73+
@Test
74+
public void testFindOne() {
75+
User user = userRepositoryImpl.save(userTest1);
76+
assertNotNull(userRepositoryImpl.findOne(userTest1.getId()));
77+
78+
user = userRepositoryImpl.findOne(userTest1.getId());
79+
assertNotNull(user);
80+
assertEquals(user.getId(), userTest1.getId());
81+
}
82+
//ETC ...
83+
}
84+
85+
Rien de spécial, on injecte notre repository et on peut ensuite tester toutes les fonctions.
86+
87+
## 1er bilan :
88+
89+
* Avantages :
90+
91+
* pour ceux qui connaissent Hades, on est très proche du mode de fonctionnement;
92+
* les fonctions CRUD déjà implémentées;
93+
* le mode implémentation automatique permet de gagner du temps dans les petits développements.
94+
95+
* Limitations :
96+
97+
* les interfaces peuvent vite devenir confuses avec des FindByXXXandYYY, FindByXXXandYYYOrderBy, ...
98+
* les requêtes persos ne sont pas vérifiées avant l’exécution (aie aux tests unitaires oubliés)
99+
100+
## Cas d’utilisation avancé
101+
### 1)Ajouter des comportements au repository
102+
103+
public interface UserRepositoryCustom {
104+
public boolean customMethod(User user);
105+
}
106+
107+
@Repository("userRepositoryImpl")
108+
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
109+
private static final Logger LOGGER = LoggerFactory.getLogger(UserRepositoryCustom.class);
110+
111+
public boolean customMethod(User user){
112+
LOGGER.info("Methode ajoutee au repository : UserRepository");
113+
return true;
114+
}
115+
}
116+
117+
@Repository("userRepositoryImpl")
118+
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
119+
private static final Logger LOGGER = LoggerFactory.getLogger(UserRepositoryCustom.class);
120+
121+
public boolean customMethod(User user){
122+
LOGGER.info("Methode ajoutee au repository : UserRepository");
123+
return true;
124+
}
125+
}
126+
127+
Que du classique : A savoir la déclaration et l'implémentation des comportements que l'on souhaite ajouter à notre repository. Il s'agit d'un bean classique que l'on pourrait injecter dans une classe indépendamment de notre repository.
128+
129+
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom{
130+
User findByUsername(String username);
131+
132+
List<User> findByUsernameAndAge(String username, Integer age);
133+
134+
Page<User> findByUsernameLike(String username, Pageable pageable);
135+
136+
@Query("SELECT u FROM User u WHERE u.username like ?1")
137+
Page<User> findByUsernameLikeCusom(String username, Pageable pageable);
138+
139+
List<User> findByAgeBetween(Integer min, Integer max);
140+
}
141+
142+
On rajoute à notre repository "UserRepository" un extends sur notre repository UserRepositoryCustom et hop on profite des fonctionnalités de spring-data-jpa plus celles de notre implémentation de UserRepositoryCustom
143+
144+
A savoir qu'il est possible d'ajouter des comportements "par défaut" à tous les repositories. [(cf la doc de spring-data)]( http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-behaviour-for-all-repositories).
145+
146+
public class UserRepositoryTest {
147+
//...
148+
149+
@Autowired
150+
private UserRepository userRepositoryImpl;
151+
152+
@Test
153+
public void testCustomMethod() {
154+
boolean result = userRepositoryImpl.customMethod(userTest1);
155+
assertTrue(result);
156+
}
157+
//.....
158+
}
159+
Rien de particulier, on teste que notre UserRepository profite bien de la fonction définie dans notre UserRepositoryCustom.
160+
161+
### 2)Utilisation de queryDsl
162+
QueryDsl est un framework qui permet d'écrire des requêtes type-safe dans un langage humainement compréhensible.
163+
Grâce à QueryDsl on va pouvoir supprimer une des limites énoncée dans le 1er bilan et éviter pas mal de surprise à l'éxécution. On va même discrètement rajouter un peu de métier dans autre objet domain.
164+
165+
#### 1er étape : Génération des classes Q*
166+
167+
Afin de pouvoir utiliser les classes QXXX (ici QUser) il faut les générer. Il existe un plugin Maven dédié à ce travail.
168+
169+
<plugin>
170+
<groupId>com.mysema.maven</groupId>
171+
<artifactId>maven-apt-plugin</artifactId>
172+
<version>1.0.2</version>
173+
<executions>
174+
<execution>
175+
<phase>generate-sources</phase>
176+
<goals>
177+
<goal>process</goal>
178+
</goals>
179+
<configuration>
180+
<outputDirectory>target/generated-sources</outputDirectory>
181+
<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
182+
</configuration>
183+
</execution>
184+
</executions>
185+
</plugin>
186+
187+
Rem : Pour les personnes sous Eclipse il faut penser à faire un "update project configuaration".
188+
189+
#### 2ème étape : Utilisation de QueryDsl dans les repositories
190+
191+
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom, QueryDslPredicateExecutor<User>{
192+
User findByUsername(String username);
193+
194+
List<User> findByUsernameAndAge(String username, Integer age);
195+
196+
Page<User> findByUsernameLike(String username, Pageable pageable);
197+
198+
@Query("SELECT u FROM User u WHERE u.username like ?1")
199+
Page<User> findByUsernameLikeCusom(String username, Pageable pageable);
200+
201+
List<User> findByAgeBetween(Integer min, Integer max);
202+
}
203+
204+
#### 3ème étape : On teste
205+
206+
public class UserRepositoryTest {
207+
//...
208+
209+
@Test
210+
public void testQueryDsl() {
211+
List<User> users = userRepositoryImpl.save(usersTest);
212+
users = userRepositoryImpl.findAll();
213+
assertNotNull(users);
214+
assertTrue(users.size() == 3);
215+
216+
users = (List<User>) userRepositoryImpl.findAll(QUser.user.username.like("Test%").and (QUser.user.age.eq(userTest1.getAge())));
217+
assertNotNull(users);
218+
assertTrue(users.size() == 1);
219+
assertTrue(users.get(0).getId() == userTest1.getId());
220+
assertTrue(users.size() == 1); assertTrue(users.get(0).getAge() < 18);
221+
}
222+
//....
223+
}
224+
225+
Et hop, on peut profiter de tout un langage pour générer ses requêtes type-safe! [voir la document QueryDsl]( http://source.mysema.com/static/querydsl/2.2.0/reference/html). La complétion rajoute vraiment un confort non négligeable.
226+
227+
#### 4ème étape : Enrichissement du modèle avec les prédicats
228+
229+
@Entity
230+
public class User {
231+
@Id
232+
@GeneratedValue(strategy = GenerationType.AUTO)
233+
private Long id;
234+
235+
@Column(unique = true, nullable = false)
236+
private String username;
237+
238+
@Column(nullable = false)
239+
private Integer age;
240+
241+
public static BooleanExpression isMinor() {
242+
return QUser.user.age.lt(18);
243+
}
244+
//GET et SET
245+
}
246+
247+
Ici, on voit le côté DDD et l'ajout de métier dans le modèle.
248+
249+
#### 5ème étape : On teste les prédicats
250+
251+
public class UserRepositoryTest {
252+
//...
253+
@Test
254+
public void testQueryDsl2() {
255+
List<User> users = userRepositoryImpl.save(usersTest);
256+
users = userRepositoryImpl.findAll();
257+
assertNotNull(users);
258+
assertTrue(users.size() == 3);
259+
260+
users = (List<User>) userRepositoryImpl.findAll(User.isMinor());
261+
assertNotNull(users);
262+
assertTrue(users.size() == 1);
263+
assertTrue(users.get(0).getAge() < 18);
264+
}
265+
//...
266+
}
267+
268+
On peut maintenant utiliser les prédicats prédéfinis pour générer des requêtes.
269+
270+
## 2ème bilan :
271+
* On retrouve biens les concepts d'Hades et la possibilité d'étendre les repositories afin de rajouter des comportements.
272+
* L'utilisation du QueryDsl est vraiment intéressante. On peut fabriquer des requêtes type-safe et dans un langue proche de langage courant et on profite de la complétion!. On évite aussi de rajouter toutes les 5secondes une nouvelle méthode dans le repostitory (cela évite d'avoir plusieurs dizaines findByXXXandYYY, ...).

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy