Repository en Spring

Gestion des requêtes SQL avec Spring Data

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com

Dans l'article consacré au Java Peristence API et Hibernate on a brièvement abordé la notion des repositories. Cet article présentera plus en détail cet aspect et montrera comment le mettre en place.

Repository

Repositories sont des interfaces héritant de l'interface Repository. L'objectif de ces interfaces consiste à rendre la création de la couche d'accès aux données (requêtes SELECT, UPDATE...) plus rapide.

On n'est donc pas obligés d'écrire des requêtes supplémentaires pour retrouver une entité, par exemple, par son identifiant ou de retrouver toutes les entités disponibles. Les méthodes findById() et findAll() sont là pour cela. En plus, on peut utiliser les méthodes liées au CRUD (Create, Read, Update, Delete) sans aucun effort supplémentaire.

En outre, la recherche des éléments par des simples clauses WHERE est possible grâce au modèle findByAddress() où "Address" signifie l'attribut address de l'entité qu'on veut récupérer.

On peut également créer des requêtes plus complexes. Pour cela, on emploiera l'annotation @Query directement au-dessus de la méthode qui doit exécuter la requête. On privilégiera cette technique dans notre application de test à cause de sa facilité de compréhension. Les requêtes seront écrites en JPQL.

Passons maintenant à quelques exemple issus de notre projet :

public interface AdminRepository extends CrudRepository<Admin, Long> {
    @Query("SELECT a FROM Admin a")
    public List<Admin> getAvailableAdmin();
}

Dans ce code l'interface AdminRepository hérite de l'interface CrudRepository qui supporte la gestion des opérations CRUD. Ce repository gère l'entité Admin qui contient une clé primaire de la classe Long. Maintenant la partie important, @Query qui définit la requête qui récupère la liste des administrateurs. On pourrait aussi bien utiliser findAll. Cependant, on a créé cette sélection pour afficher comment écrire une requête toute simple, sans paramètres ni d'autres éléments supplémentaires.

public interface WriterLangRepository  extends CrudRepository<WriterLang, WriterLangPK> {

    @Transactional(readOnly = true)
    @Query("select wl from WriterLang wl where writer = :writer")
    public List<WriterLang> getForWriter(@Param("writer") Writer writer);
}

La requête ci-dessus recherche toutes les traductions pour un écrivain donné. On constate que le paramètre dans la requête est précédé par ":". Il correspond à la valeur de l'annotation @Param et dans cette situation il s'agit du writer.

public interface SubscriberRepository  extends CrudRepository<Subscriber, Long> {
    @Query("SELECT s FROM Subscriber s WHERE DATEDIFF(s.created, CURDATE()) = :days AND s.confirmed = :confirmed AND s.revival < :days")
    public List<Subscriber> getNonConfirmedByDays(@Param("days") int days, @Param("confirmed") int confirmed);
}

Les requêtes peuvent donc utiliser certaines fonctions SQL.

// NewsletterRepository.java
public interface NewsletterRepository extends CrudRepository<Newsletter, Long> {
    @Query("SELECT n, a FROM Newsletter n JOIN n.admin a WHERE n.state != :state AND n.sendTime < :date ORDER BY n.sendTime ASC")
    public List<Object[]> getToSend(@Param("date") Date date, @Param("state") int state);
}

// Admin.java
@Entity
@Table(name = "admin")
public class Admin implements Serializable {
    @OneToMany(mappedBy = "admin")
    public List getNewsletters() {
        return newsletters;
    }
}

// Newsletter.java
@Entity
@Table(name="newsletter")
public class Newsletter implements Serializable {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "admin_id_ad")
    public Admin getAdmin() {
        return admin;
    }
}

Cet exemple montre l'utilisation d'un repository dans une requête basée sur les jointures. En analysant les entités, on remarque qu'un Admin peut avoir plusieurs Newsletter et qu'une instance de cette dernière correspond à une seule instance Admin. Dans notre requête on veut récupérer à chaque fois la paire Newsletter - Admin. La réponse retournée est une liste contenant un tableau des objets. Ensuite on peut récupérer les instances des 2 entités dans l'ordre dans laquelle elles sont présentes après le mot SELECT. Dans ce cas, le premier (0) élément du tableau représentera l'instance Newsletter et le second (1) l'instance Admin.

public interface NewsletterPreferencyCategoryRepository extends CrudRepository<NewsletterPreferencyCategory, Long> {
    @Query("SELECT npc FROM NewsletterPreferencyCategory npc WHERE npc.code IN :codes")
    public List<NewsletterPreferencyCategory> getFromCodes(@Param("codes") List<String> codes);
}

Ici on voit comment créer un type de requête qui est toujours difficilement gérée par les ORM. Il s'agit de celle qui recherche des éléments par rapport à une clause IN. On voit que pour rechercher des éléments par rapport à une liste des noms, il suffit de passer une liste dans le paramètre.

// BookLangRepository.java
public interface BookLangRepository  extends CrudRepository<BookLang, BookLangPK>, PagingAndSortingRepository<BookLang, BookLangPK> {
    @Transactional(readOnly = true)
    @Query("SELECT bl, bc FROM BookLang bl, BookCategory bc WHERE bl.lang = :lang AND bl.bookLangPK.type = :type")
    public Page<BookLang> findAllForLangAndType(@Param("lang") Lang lang, @Param("type") String type, Pageable pageable);
}

// BookLangServiceImpl.java
@Service("bookLangService")
public class BookLangServiceImpl implements BookLangService {
    @Autowired
    private BookLangRepository bookLangRepository;

    @Override
    public Page<BookLang> findAllByTitleForLang(int page, int perPage, Lang lang) {
        Pageable pageable = new PageRequest(page, perPage);
        return bookLangRepository.findAllForLangAndType(lang, "titl", pageable);
    }
}

Cette dernière requête présentée gère l'affichage des traductions avec un LIMIT. Pour la marquer en tant que telle, elle doit retourner Page et prendre en paramètre l'instance de la classe Pageable.

// SubscriberRepository  .java
public interface SubscriberRepository  extends CrudRepository&t;Subscriber, Long> {
    // ...
    @Modifying
    @Query("UPDATE Subscriber s SET s.password = :password WHERE s.id = :id")
    public void updatePassword(@Param("password") String password, @Param("id") long id);
}

Ici on voit comment effectuer les opérations d'écriture sur une table. En occurrence, on va mettre à jour le champ password de l'entité Subscriber. On remarque l'utilisation des mêmes paramètres (@Param) pour des données associées à la requête.

Bartosz KONIECZNY Couche des données

Une question ? Une remarque ?

*

*

Un conseil Symfony1

Comment appeler un helper dans l'action ?

L'une des méthodes pour utiliser un view helper dans l'action est: sfProjectConfiguration::getActive()->loadHelpers(array("mon_helper")); mon_helper();