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 d' isoler la couche application / métier de la couche de persistance (généralement une base de données relationnelle, mais il peut s'agir de tout autre mécanisme de persistance) à l'aide d'une API abstraite .

La fonctionnalité de cette API est de cacher à l'application toutes les complexités impliquées dans l'exécution des 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'une de l'autre.

Dans ce didacticiel, nous allons plonger en profondeur dans l'implémentation du modèle et nous allons apprendre à l'utiliser pour extraire les appels à un gestionnaire d'entités JPA.

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éer une classe DAO simple qui se chargera de garder ces composants parfaitement découplés les uns des autres .

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 classe User 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 sûr, le choix de conception le plus pertinent que nous devons faire ici est de savoir comment garder l'application qui utilise cette classe isolée de tout mécanisme de persistance qui pourrait ê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 une couche DAO de base, afin que nous puissions voir comment elle peut garder le modèle de domaine complètement découplé de la couche de persistance.

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); }

D'un point de vue des yeux d'oiseau, il est évident de voir que le Dao interface définit une API abstraite qui effectue des opérations CRUD sur des objets de type T .

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 objets User .

2.3. La classe UserDao

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

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 classe UserDao implémente toutes les fonctionnalités requises pour récupérer, mettre à jour et supprimer des objets User .

Par souci de simplicité, la liste des utilisateurs agit comme une base de données en mémoire, qui est remplie avec quelques objets User dans le constructeur .

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

Bien que les classes User et UserDao 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 de l'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 derrière le modèle DAO. Dans ce cas, la méthode principale utilise simplement une instance UserDao pour effectuer des opérations CRUD sur quelques objets User .

La facette la plus pertinente de ce processus est la façon dont UserDao cache à l'application tous les détails de bas niveau sur la façon dont les objets sont conservés, mis à jour et supprimés .

3. Utilisation du modèle avec JPA

Les développeurs ont généralement tendance à penser que la publication de JPA a réduit à 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.

Incontestablement, dans certains scénarios, cela est vrai. Même ainsi , parfois, nous voulons simplement exposer à notre application uniquement quelques méthodes spécifiques au domaine de l'API du gestionnaire d'entités. Dans de tels cas, le modèle DAO a sa place.

3.1. La classe JpaUserDao

Cela dit, créons une nouvelle implémentation de l' interface Dao , 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 classe JpaUserDao 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 réaliserons comment l'utilisation de la composition et de l'injection de dépendances nous permet d'appeler uniquement les méthodes de 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 classe d' utilisateurs

Dans ce cas, nous utiliserons Hibernate comme implémentation par défaut de JPA, nous allons donc refactoriser la classe User 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

Assuming that we already have a working instance of MySQL running either locally or remotely and a database table “users” populated with some user records, we need to get a JPA entity manager, so we can use the JpaUserDao class for performing CRUD operations in the database.

In most cases, we accomplish this via the typical “persistence.xml” file, which is the standard approach.

In this case, we'll take an “xml-less” approach and get the entity manager with plain Java through Hibernate's handy EntityManagerFactoryBuilderImpl class.

For a detailed explanation on how to bootstrap a JPA implementation with Java, please check this article.

3.4. The UserApplication Class

Finally, let's refactor the initial UserApplication class, so it can work with a JpaUserDao instance and execute CRUD operations on the User entities:

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); } }

Even when the example is pretty limited indeed, it remains useful for demonstrating how to integrate the DAO pattern's functionality with the one that the entity manager provides.

In most applications, there's a DI framework, which is responsible for injecting a JpaUserDao instance into the UserApplication class. For simplicity's sake, we've omitted the details of this process.

Le point le plus pertinent à souligner ici est la façon dont la classe JpaUserDao aide à garder la classe UserApplication complètement indépendante de la façon dont la couche de persistance effectue les opérations CRUD .

De plus, nous pourrions échanger MySQL contre tout 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' interface Dao et le gestionnaire d'entités .

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 disponibles à l'adresse over sur GitHub.