Il pattern DAO in Java

1. Panoramica

Il modello DAO (Data Access Object) è un modello strutturale che ci consente di isolare il livello applicazione / aziendale dal livello di persistenza (solitamente un database relazionale, ma potrebbe essere qualsiasi altro meccanismo di persistenza) utilizzando un'API astratta .

La funzionalità di questa API consiste nel nascondere all'applicazione tutte le complessità coinvolte nell'esecuzione delle operazioni CRUD nel meccanismo di archiviazione sottostante. Ciò consente a entrambi i livelli di evolversi separatamente senza sapere nulla l'uno dell'altro.

In questo tutorial, faremo un'immersione approfondita nell'implementazione del pattern e impareremo come usarlo per astrarre le chiamate a un gestore di entità JPA.

2. Una semplice implementazione

Per capire come funziona il pattern DAO, creiamo un esempio di base.

Diciamo che vogliamo sviluppare un'applicazione che gestisca gli utenti. Per mantenere il modello di dominio dell'applicazione completamente indipendente dal database, creeremo una semplice classe DAO che si occuperà di mantenere questi componenti ben separati l'uno dall'altro .

2.1. La classe di dominio

Poiché la nostra applicazione funzionerà con gli utenti, dobbiamo definire solo una classe per implementare il suo modello di dominio:

public class User { private String name; private String email; // constructors / standard setters / getters }

La classe User è solo un semplice contenitore per i dati dell'utente, quindi non implementa nessun altro comportamento che valga la pena sottolineare.

Ovviamente, la scelta progettuale più rilevante che dobbiamo fare qui è come mantenere l'applicazione che utilizza questa classe isolata da qualsiasi meccanismo di persistenza che potrebbe essere implementato a un certo punto.

Bene, questo è esattamente il problema che il pattern DAO tenta di risolvere.

2.2. L'API DAO

Definiamo un livello DAO di base, in modo da poter vedere come può mantenere il modello di dominio completamente disaccoppiato dal livello di persistenza.

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

A volo d'uccello, è chiaro a vedere che il Tao interfaccia definisce un'API astratta che esegue operazioni CRUD su oggetti di tipo T .

A causa dell'alto livello di astrazione fornito dall'interfaccia, è facile creare un'implementazione concreta a grana fine che funzioni con gli oggetti utente .

2.3. La classe UserDao

Definiamo un'implementazione specifica dell'utente dell'interfaccia 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 implementa tutte le funzionalità richieste per il recupero, l'aggiornamento e la rimozione degli oggetti User .

Per semplicità, l' elenco degli utenti si comporta come un database in memoria, popolato con un paio di oggetti Utente nel costruttore .

Ovviamente è facile refactoring degli altri metodi, in modo che possano funzionare, ad esempio, con un database relazionale.

Mentre entrambe le classi User e UserDao coesistono indipendentemente all'interno della stessa applicazione, dobbiamo ancora vedere come quest'ultima possa essere utilizzata per mantenere il livello di persistenza nascosto dalla logica dell'applicazione:

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'esempio è artificioso, ma mostra, in poche parole, le motivazioni alla base del pattern DAO. In questo caso, il metodo principale utilizza solo un'istanza di UserDao per eseguire operazioni CRUD su alcuni oggetti utente .

L'aspetto più rilevante di questo processo è il modo in cui UserDao nasconde all'applicazione tutti i dettagli di basso livello su come gli oggetti vengono mantenuti, aggiornati ed eliminati .

3. Utilizzo del pattern con JPA

C'è una tendenza generale tra gli sviluppatori a pensare che il rilascio di JPA abbia ridotto a zero la funzionalità del pattern DAO, poiché il pattern diventa solo un altro livello di astrazione e complessità implementato oltre a quello fornito dall'entità manager di JPA.

Indiscutibilmente, in alcuni scenari questo è vero. Anche così , a volte vogliamo solo esporre alla nostra applicazione solo alcuni metodi specifici del dominio dell'API del gestore entità. In questi casi, il pattern DAO ha il suo posto.

3.1. La classe JpaUserDao

Detto questo, creiamo una nuova implementazione dell'interfaccia Dao , così possiamo vedere come può incapsulare la funzionalità che il gestore di entità di JPA fornisce fuori dagli schemi:

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 è in grado di funzionare con qualsiasi database relazionale supportato dall'implementazione JPA.

Inoltre, se guardiamo da vicino la classe, ci renderemo conto di come l'uso di Composition and Dependency Injection ci permetta di chiamare solo i metodi del gestore di entità richiesti dalla nostra applicazione.

In poche parole, abbiamo un'API su misura specifica per il dominio, piuttosto che l'intera API del gestore entità.

3.2. Refactoring della classe utente

In questo caso, useremo Hibernate come implementazione predefinita di JPA, quindi rifattorizzeremo la classe User di conseguenza:

@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. Avvio di un gestore entità JPA a livello di programmazione

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.

Il punto più rilevante da sottolineare qui è come la classe JpaUserDao aiuta a mantenere la classe UserApplication completamente agnostica su come il livello di persistenza esegue le operazioni CRUD .

Inoltre, potremmo scambiare MySQL con qualsiasi altro RDBMS (e anche per un database piatto) più avanti, e tuttavia, la nostra applicazione continuerebbe a funzionare come previsto, grazie al livello di astrazione fornito dall'interfaccia Dao e dal gestore di entità .

4. Conclusione

In questo articolo, abbiamo esaminato in modo approfondito i concetti chiave del pattern DAO, come implementarlo in Java e come utilizzarlo sopra l'entità manager di JPA.

Come al solito, tutti gli esempi di codice mostrati in questo articolo sono disponibili su GitHub.