Le modèle DAO en Java

Le modèle DAO en Java

1. Vue d'ensemble

Le modèle DAO (Data Access Object) est un modèle structurel qui nous permet deisolate the application/business layer from the persistence layer (usually a relational database, but it could be any other persistence mechanism) using an abstract API.

La fonctionnalité de cette API consiste à masquer à l'application toutes les complexités inhérentes à l'exécution d'opérations CRUD dans le mécanisme de stockage sous-jacent. Cela permet aux deux couches d'évoluer séparément sans rien savoir l'un de l'autre.

Dans ce didacticiel, nous allons approfondir la mise en œuvre du modèle et apprendre à l'utiliser pour extraire les appels à unJPA entity manager.

Lectures complémentaires:

Introduction à Spring Data JPA

Introduction à Spring Data JPA avec Spring 4 - la configuration de Spring, le DAO, les requêtes manuelles et générées et la gestion des transactions.

Read more

Présentation des types JPA / Hibernate Cascade

Un aperçu rapide et pratique des types de cascade JPA / Hibernate.

Read more

2. Une mise en œuvre simple

Pour comprendre le fonctionnement du modèle DAO, créons un exemple de base.

Disons que nous voulons développer une application qui gère les utilisateurs. Pour que le modèle de domaine de l'application reste totalement indépendant de la base de données, nous allons créera simple DAO class that will take care of keeping these components neatly decoupled from each other.

2.1. La classe de domaine

Comme notre application fonctionnera avec les utilisateurs, nous devons définir une seule classe pour implémenter son modèle de domaine:

public class User {

    private String name;
    private String email;

    // constructors / standard setters / getters
}

La classeUser n'est qu'un simple conteneur pour les données utilisateur, elle n'implémente donc aucun autre comportement qui mérite d'être souligné.

Bien entendu, le choix de conception le plus pertinent que nous devons faire ici est de savoir comment garder l'application qui utilise cette classe est isolée de tout mécanisme de persistance pouvant être implémenté à un moment donné.

Eh bien, c’est exactement le problème que le modèle DAO tente de résoudre.

2.2. L'API DAO

Définissons un calque DAO de base, afin que nous puissions voir comment il peutkeep the domain model completely decoupled from the persistence layer.

Voici l'API DAO:

public interface Dao {

    Optional get(long id);

    List getAll();

    void save(T t);

    void update(T t, String[] params);

    void delete(T t);
}

À vol d'oiseau, il est clair que l'interfaceDao définit une API abstraite qui effectue des opérations CRUD sur des objets de typeT.

En raison du haut niveau d'abstraction fourni par l'interface, il est facile de créer une implémentation concrète et fine qui fonctionne avec les objetsUser.

2.3. La classeUserDao

Définissons une implémentation spécifique à l'utilisateur de l'interfaceDao:

public class UserDao implements Dao {

    private List users = new ArrayList<>();

    public UserDao() {
        users.add(new User("John", "[email protected]"));
        users.add(new User("Susan", "[email protected]"));
    }

    @Override
    public Optional get(long id) {
        return Optional.ofNullable(users.get((int) id));
    }

    @Override
    public List getAll() {
        return users;
    }

    @Override
    public void save(User user) {
        users.add(user);
    }

    @Override
    public void update(User user, String[] params) {
        user.setName(Objects.requireNonNull(
          params[0], "Name cannot be null"));
        user.setEmail(Objects.requireNonNull(
          params[1], "Email cannot be null"));

        users.add(user);
    }

    @Override
    public void delete(User user) {
        users.remove(user);
    }
}

La classeUserDao implémente toutes les fonctionnalités requises pour récupérer, mettre à jour et supprimer les objetsUser.

For simplicity’s sake, the users List acts like an in-memory database, which is populated with a couple of User objects in the constructor.

Bien entendu, il est facile de refactoriser les autres méthodes afin qu’elles puissent fonctionner, par exemple, avec une base de données relationnelle.

Alors que les classesUser etUserDao coexistent indépendamment dans la même application, nous devons encore voir comment cette dernière peut être utilisée pour garder la couche de persistance cachée de la logique d'application:

public class UserApplication {

    private static Dao userDao;

    public static void main(String[] args) {
        userDao = new UserDao();

        User user1 = getUser(0);
        System.out.println(user1);
        userDao.update(user1, new String[]{"Jake", "[email protected]"});

        User user2 = getUser(1);
        userDao.delete(user2);
        userDao.save(new User("Julie", "[email protected]"));

        userDao.getAll().forEach(user -> System.out.println(user.getName()));
    }

    private static User getUser(long id) {
        Optional user = userDao.get(id);

        return user.orElseGet(
          () -> new User("non-existing user", "no-email"));
    }
}

L'exemple est artificiel, mais il montre en un mot les motivations qui sous-tendent le motif DAO. Dans ce cas, la méthodemain utilise simplement une instanceUserDao pour effectuer des opérations CRUD sur quelques objetsUser.

The most relevant facet of this process is how UserDao hides from the application all the low-level details on how the objects are persisted, updated, and deleted.

3. Utilisation du modèle avec JPA

Les développeurs ont généralement tendance à penser que la publication de JPA a ramené à zéro la fonctionnalité du modèle DAO, car le modèle devient juste une autre couche d'abstraction et de complexité implémentée en plus de celle fournie par le gestionnaire d'entités de JPA.

Indiscutablement, dans certains scénarios, cela est vrai. Même si, sometimes we just want to expose to our application only a few domain-specific methods of the entity manager’s API. Dans de tels cas, le modèle DAO a sa place.

3.1. La classeJpaUserDao

Cela dit, créons une nouvelle implémentation de l'interfaceDao, afin que nous puissions voir comment elle peut encapsuler les fonctionnalités fournies par le gestionnaire d'entités de JPA:

public class JpaUserDao implements Dao {

    private EntityManager entityManager;

    // standard constructors

    @Override
    public Optional get(long id) {
        return Optional.ofNullable(entityManager.find(User.class, id));
    }

    @Override
    public List getAll() {
        Query query = entityManager.createQuery("SELECT e FROM User e");
        return query.getResultList();
    }

    @Override
    public void save(User user) {
        executeInsideTransaction(entityManager -> entityManager.persist(user));
    }

    @Override
    public void update(User user, String[] params) {
        user.setName(Objects.requireNonNull(params[0], "Name cannot be null"));
        user.setEmail(Objects.requireNonNull(params[1], "Email cannot be null"));
        executeInsideTransaction(entityManager -> entityManager.merge(user));
    }

    @Override
    public void delete(User user) {
        executeInsideTransaction(entityManager -> entityManager.remove(user));
    }

    private void executeInsideTransaction(Consumer action) {
        EntityTransaction tx = entityManager.getTransaction();
        try {
            tx.begin();
            action.accept(entityManager);
            tx.commit();
        }
        catch (RuntimeException e) {
            tx.rollback();
            throw e;
        }
    }
}

La classeJpaUserDao est capable de fonctionner avec n'importe quelle base de données relationnelle prise en charge par l'implémentation JPA.

De plus, si nous regardons de près la classe, nous nous rendrons compte à quel point l’utilisation deComposition etDependency Injection nous permet d’appeler uniquement les méthodes du gestionnaire d’entités requises par notre application.

En termes simples, nous avons une API personnalisée spécifique au domaine, plutôt que l’API entière du gestionnaire d’entités.

3.2. Refactoriser la classeUser

Dans ce cas, nous utiliserons Hibernate comme implémentation par défaut de JPA, nous allons donc refactoriser la classeUser en conséquence:

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String name;
    private String email;

    // standard constructors / setters / getters
}

3.3. Amorcer un gestionnaire d'entité JPA par programme

En supposant que nous ayons déjà une instance fonctionnelle de MySQL exécutée localement ou à distance et une table de base de données“users” remplie avec quelques enregistrements utilisateur, nous devons obtenir un gestionnaire d'entités JPA, afin que nous puissions utiliser la classeJpaUserDao pour effectuer des opérations CRUD dans la base de données.

Dans la plupart des cas, nous accomplissons cela via le fichier typique“persistence.xml”, qui est l'approche standard.

Dans ce cas, nous allons adopter une approche“xml-less” et obtenir le gestionnaire d’entités avec Java clair via la classe pratiqueEntityManagerFactoryBuilderImpl d’Hibernate.

Pour une explication détaillée sur la façon d'amorcer une implémentation JPA avec Java, veuillez vérifierthis article.

3.4. La classeUserApplication

Enfin, refactorisons la classe initialeUserApplication, afin qu'elle puisse fonctionner avec une instanceJpaUserDao et exécuter des opérations CRUD sur les entitésUser:

public class UserApplication {

    private static Dao jpaUserDao;

    // standard constructors

    public static void main(String[] args) {
        User user1 = getUser(1);
        System.out.println(user1);
        updateUser(user1, new String[]{"Jake", "[email protected]"});
        saveUser(new User("Monica", "[email protected]"));
        deleteUser(getUser(2));
        getAllUsers().forEach(user -> System.out.println(user.getName()));
    }

    public static User getUser(long id) {
        Optional user = jpaUserDao.get(id);

        return user.orElseGet(
          () -> new User("non-existing user", "no-email"));
    }

    public static List getAllUsers() {
        return jpaUserDao.getAll();
    }

    public static void updateUser(User user, String[] params) {
        jpaUserDao.update(user, params);
    }

    public static void saveUser(User user) {
        jpaUserDao.save(user);
    }

    public static void deleteUser(User user) {
        jpaUserDao.delete(user);
    }
}

Même lorsque l’exemple est assez limité, il reste utile pour montrer comment intégrer la fonctionnalité du modèle DAO à celle fournie par le gestionnaire d’entités.

Dans la plupart des applications, il existe un framework DI, qui est responsable de l'injection d'une instanceJpaUserDao dans la classeUserApplication. Par souci de simplicité, nous avons omis les détails de ce processus.

Le point le plus pertinent à souligner ici est de savoir commentthe JpaUserDao class helps to keep the UserApplication class completely agnostic about how the persistence layer performs CRUD operations.

De plus, nous pourrions échanger MySQL contre n'importe quel autre SGBDR (et même pour une base de données plate) plus loin, et malgré tout, notre application continuerait à fonctionner comme prévu, grâce au niveau d'abstraction fourni par l'interfaceDao et le gestionnaire d'entité.

4. Conclusion

Dans cet article, nous avons examiné en profondeur les concepts clés du modèle DAO, comment l'implémenter en Java et comment l'utiliser en plus du gestionnaire d'entités de JPA.

Comme d'habitude, tous les exemples de code présentés dans cet article sont disponiblesover on GitHub.