Nous allons nous attarder sur Spring-Data-JPA, qui fournit un ensemble d'automatisations pour JPA.
Repositories
Les repositories sont des interfaces pour lesquelles nous n'avons pas à fournir d'implémentation. Ces interfaces seront implémentées par le framework lui même au démarrage du contexte Spring.
On considère la classe Person :
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private Integer age;
private String city;
//getters & setters
}
Si on écrit par exemple :
public interface PersonRepository extends JpaRepository<Person, Long> {
List<Person> findByCity(String city);
}
Spring implémentera une requête en se basant sur le nom de la méthode, pour rechercher des personnes par la ville où elles habitent.
Pour utiliser plusieurs paramètres on utilise le mot clé "And" :
List<Person> findByFirstNameAndLastName(String firstName, String lastName);
On peut utiliser tout un tas d'autres mots clés dans le nom des méthodes pour construire nos requêtes :
- And
- Or
- GreaterThan
- LessThan
- Like
- IsNull
- OrderBy
- ...
List<Person> findByAgeOrderByLastnameDesc(Integer age);
Les repositories permettent aussi de persister les entités dans la base :
personRepository.save(person);
Ces fonctionnalités sont directement reprises depuis Hades, les 2 frameworks partageant le même spec leader.
Si on veut écrire directement une requête JPQL, on peut utiliser l'annotation @Query :
@Query("ma super requête ici")
List<Person> findWithMyCustomQuery(Customer customer);
La requête sera exécutée lorsque qu'on appellera la méthode findWithMyCustomQuery.
Named queries
Pour utiliser une NamedQuery, il suffit que le nom de la méthode du repository suive le même nom que la NamedQuery. Dans ce cas de figure, le framework n'essaiera pas de générer une requête en suivant le nom de la méthode.
Par exemple la méthode findByNameWithMyFilters sera mappée sur la requête nommée "Person.findByNameWithMyFilters".
Entité :
@Entity
@NamedQuery(name = "Person.findByNameWithMyFilters",
query = "select p from Person p where filter1 = ? and filter2 = ?")
public class Person {...}
Repository :
Person findByNameWithMyFilters(String filter1, String filter2);
Ces repositories nous facilitent bien la vie en générant pour nous les requêtes pour un grand nombre de cas courants. Un détail qui a son importance : toutes les méthodes des repositories sont transactionnelles par défaut.
Querydsl
Querydsl est une librairie qui apporte des API pour requêter des entités de manière "typesafe" et avec une syntaxe épurée. Spring-Data supporte cette librairie pour simplifier l'écriture de prédicats en Java.
Voici quelques exemples de requêtes, avec l'utilisation des mots clés eq (equals), like et gt (greater than) :
private static QPerson person = QPerson.person;
public static BooleanExpression fromCity(String city) {
return person.city.like(city);
}
public static BooleanExpression byName(String firstName, String lastName) {
return person.firstName.eq(firstName).and(person.lastName.eq(lastName));
}
public static BooleanExpression adults() {
return person.age.gt(18);
}
J'aime beaucoup cette syntaxe, elle est vraiment fluide, facile à écrire et lisible. Il est bien sur possible de gérer des cas plus complexes, je vous laisse consulter la doc de Querydsl pour en savoir plus.
Pour exécuter une requête utilisant un de ces prédicats, il suffit d'écrire :
personRepository.findAll(Person.fromCity("maVille"))
ou
personRepository.findAll(Person.adults())
Pour que tout cela fonctionne, notre repository doit implémenter QuerydslPredicateExecutor
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>maven-apt-plugin</artifactId>
<version>1.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources</outputDirectory>
<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
N.B : Il est possible d'écrire nos prédicats avec l'api criteria de JPA 2 au lieu de Querydsl si on le souhaite, en utilisant la classe org.springframework.data.jpa.domain.Specification à la place de nos BooleanExpression.
Voyons comment utiliser nos repositories et nos prédicats :
@Test
public void testPersistence() {
Person p = new Person();
p.setFirstName("loic");
p.setLastName("descotte");
p.setCity("Grenoble");
p.setAge(29);
Person p2 = new Person();
p2.setFirstName("john");
p2.setLastName("doe");
p2.setAge(17);
p2.setCity("NYC");
//save
personRepository.save(p);
personRepository.save(p2);
persons = personRepository.findAll();
assertEquals(persons.size(),2);
persons = (List) personRepository.findAll(Person.fromCity("%Gre%"));
assertEquals(persons.size(),1);
assertEquals(persons.get(0).getFirstName(),"loic");
persons = (List) personRepository.findByFirstNameAndLastName("loic", "descotte");
assertEquals(persons.size(),1);
assertEquals(persons.get(0).getCity(),"Grenoble");
persons = (List) personRepository.findAll(Person.adults());
assertEquals(persons.size(),1);
assertEquals(persons.get(0).getFirstName(),"loic");
}
Dans cet exemple le code client est une classe de test, mais on peut très bien appeler notre repository depuis un service REST, depuis la couche graphique, etc. (voir le code source de l'exemple en téléchargement à la fin de cet article).
On voit dans ces exemples qu'on a complètement modifié notre façon d'architecturer notre application Spring: on peut si on le souhaite se passer des couche "services" ou "business" qui contiennent habituellement la logique de notre application.
En effet Spring-Data et ses repositories ont un bon gout de DDD (Domain Driven Design). Ceux qui ont essayé Play connaissent déjà cette approche.
Le code métier de notre application peut être recentré sur le modèle (objets du domaine métier). A nous la vrai conception objet, fini les modèles anémiques sans intelligence dont les seules méthodes sont des getters et des setters.
Comme vous le voyez dans cet exemple, on a même la possibilité de placer nos prédicats d'accès à la base de données dans la classe Personne.
Pour conclure ce nouveau framework est vraiment prometteur et apporte pas mal de nouveautés et de facilités aux applications codées en environnement Spring.
Le seul bémol que j'ai vu étant (comme souvent avec Spring et Maven) le temps nécessaire pour mettre au point la configuration XML et trouver les bonnes dépendances (sans compter les conflits avec Jersey, etc.)
Mais comme je suis sympa vous pouvez tout recopier dans le code source des exemples de l'article : code source de l'exemple.
Je vous conseille de jeter également un oeil aux autres projets de Spring-Data, tout aussi intéressants et qui adressent le monde NoSQL (bases orientées graphes, documents, clé/valeur...).
12 commentaires:
Salut Loïc,
merci pour ton article en effet il y a bcp de choses intéressantes dans ce que tu montres. Surtout avec le QueryDSL qui permet enfin faire des requêtes SQL sans perdre les fonctionnalités de son IDE préféré (complétion & refactoring? ... a avoir comment exactement mais au moins le renommage de l'attribut d'une entitée devrait faire une erreur de compilation ce qui est déjà mieux qu'avec les criteria d'Hibernate qui sont en erreur seulement à l'exécution de la requête et sans test de toutes les requêtes t'es mort).
Juste il y a une petite coquille DDD c'est Domain Driver DESIGN (pas Development)
Nicolas LC
Salut nicolas, merci pour ton commentaire
Coquille corrigé, décidément il faut que je me relise plus!
Hello,
Article très intéressant! Merci.
J'ai une question concernant la ligne de code suivante :
person.save();
Tu indiques que les objets (vue la ligne de code, tu dois parler des entitées) sont capables de se persister eux mêmes. Est une coquille? est ce que tu veux parler des objets de type repository (dans ce cas là ca serait :
personRepository.save();
à la place de person.
Merci d'avance pour ton aide.
Cordialement
Fabszn
Hello!
Effectivement c'est une coquille, c'est corrigé, merci!
Pour sauvegarder les entités on passe bien par les repositories. La syntaxe que j'ai écrit au début c'etait du play framework, ça a fini par déformer mes habitudes je crois :)
Hello,
Je suis en train de travailler sur springdata jpa et queryDSL... Je m'appuie sur ton article pour l'intégration des 2.
Je viens de tomber sur une autre petite coquille.
Dans le dernier exemple de code la sauvegarde des de deux personnes est faite 2 fois.
Cordialement,
Fabszn
Effectivement, c'est corrigé merci!
salut
Grand Merci pour ton article ,
j ai un petit soucis, c'est que en utilisant une interface qui extends JpaRepository, mon IDE me demande de configurer le buildPath, et je sai pas qu'est ce que je dois configurer précisément
Merci de me préciser de quoi s'agit-il
Hello,
il doit te manquer une librairie.
Tu utilises maven pour résoudre les dépendances?
non je n'utilise pas maven ,
enfaite j ai déjà ajouté la librairies concernant spring data
Sans voir ton environnement je ne peux pas vraiment répondre à cette question. Mais si tu as un problème de build path il te manque surement une librairie, peut être une dépendance de spring-data si ce n'est pas la lib spring-data elle même.
est-ce qu'on ne doit pas ajouter une configuration dans applicationContext.xml
Rien de plus que dans l'exemple... Tu as récupéré le code? http://dl.dropbox.com/u/7549438/blog/Directory.zip
Enregistrer un commentaire