Das DAO-Muster in Java

1. Übersicht

Das DAO-Muster (Data Access Object) ist ein Strukturmuster, mit dem wir die Anwendungs- / Geschäftsschicht mithilfe einer abstrakten API von der Persistenzschicht (normalerweise eine relationale Datenbank, aber es kann sich auch um einen anderen Persistenzmechanismus handeln) isolieren können .

Die Funktionalität dieser API besteht darin, alle Komplexitäten, die mit der Ausführung von CRUD-Operationen im zugrunde liegenden Speichermechanismus verbunden sind, vor der Anwendung zu verbergen. Dadurch können sich beide Schichten getrennt entwickeln, ohne etwas voneinander zu wissen.

In diesem Tutorial werden wir uns eingehend mit der Implementierung des Musters befassen und lernen, wie Sie es zum Abstrahieren von Aufrufen an einen JPA-Entitätsmanager verwenden.

2. Eine einfache Implementierung

Um zu verstehen, wie das DAO-Muster funktioniert, erstellen wir ein grundlegendes Beispiel.

Angenommen, wir möchten eine Anwendung entwickeln, die Benutzer verwaltet. Um das Domänenmodell der Anwendung in Bezug auf die Datenbank völlig unabhängig zu halten, erstellen wir eine einfache DAO-Klasse, die dafür sorgt, dass diese Komponenten sauber voneinander entkoppelt sind .

2.1. Die Domänenklasse

Da unsere Anwendung mit Benutzern zusammenarbeitet, müssen wir nur eine Klasse für die Implementierung des Domänenmodells definieren:

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

Die Benutzerklasse ist nur ein einfacher Container für Benutzerdaten, sodass kein anderes Verhalten implementiert wird, das es wert ist, betont zu werden.

Die wichtigste Entwurfsentscheidung, die wir hier treffen müssen, ist natürlich, wie die Anwendung, die diese Klasse verwendet, von jeglichen Persistenzmechanismen isoliert bleibt, die irgendwann implementiert werden könnten.

Genau das versucht das DAO-Muster zu lösen.

2.2. Die DAO-API

Definieren wir eine grundlegende DAO-Schicht, damit wir sehen können, wie das Domänenmodell vollständig von der Persistenzschicht entkoppelt werden kann.

Hier ist die DAO-API:

public interface Dao { Optional get(long id); List getAll(); void save(T t); void update(T t, String[] params); void delete(T t); }

Aus der Vogelperspektive ist ersichtlich, dass die Dao- Schnittstelle eine abstrakte API definiert, die CRUD-Operationen für Objekte vom Typ T ausführt .

Aufgrund des hohen Abstraktionsgrades, den die Schnittstelle bietet, ist es einfach, eine konkrete, feinkörnige Implementierung zu erstellen, die mit Benutzerobjekten funktioniert .

2.3. Die UserDao- Klasse

Definieren wir eine benutzerspezifische Implementierung der Dao- Oberfläche:

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

Die UserDao- Klasse implementiert alle Funktionen, die zum Abrufen, Aktualisieren und Entfernen von Benutzerobjekten erforderlich sind.

Der Einfachheit halber verhält sich die Benutzerliste wie eine speicherinterne Datenbank, die im Konstruktor mit einigen Benutzerobjekten gefüllt ist .

Natürlich ist es einfach, die anderen Methoden umzugestalten, damit sie beispielsweise mit einer relationalen Datenbank arbeiten können.

Während sowohl die User- als auch die UserDao- Klasse unabhängig voneinander in derselben Anwendung existieren, müssen wir noch sehen, wie letztere verwendet werden kann, um die Persistenzschicht vor der Anwendungslogik zu verbergen:

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

Das Beispiel ist erfunden, zeigt aber auf den Punkt gebracht die Motivationen hinter dem DAO-Muster. In diesem Fall wird das Haupt verwendet Methode nur eine UserDao Instanz CRUD - Operationen auf ein paar auszuführen Benutzerobjekte.

Die wichtigste Facette dieses Prozesses ist, wie UserDao alle Details auf niedriger Ebene darüber verbirgt, wie die Objekte beibehalten , aktualisiert und gelöscht werden .

3. Verwenden des Musters mit JPA

Entwickler neigen allgemein dazu zu glauben, dass die Veröffentlichung von JPA die Funktionalität des DAO-Musters auf Null herabgestuft hat, da das Muster nur eine weitere Ebene der Abstraktion und Komplexität darstellt, die zusätzlich zu der vom Entitätsmanager von JPA bereitgestellten implementiert wird.

In einigen Szenarien ist dies zweifellos der Fall. Trotzdem möchten wir manchmal nur einige domänenspezifische Methoden der API des Entitätsmanagers für unsere Anwendung verfügbar machen. In solchen Fällen hat das DAO-Muster seinen Platz.

3.1. Die JpaUserDao- Klasse

Nachdem dies gesagt ist, erstellen wir eine neue Implementierung der Dao- Schnittstelle, damit wir sehen können, wie die vom Entitätsmanager von JPA sofort bereitgestellte Funktionalität gekapselt werden kann:

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

Die JpaUserDao- Klasse kann mit jeder relationalen Datenbank arbeiten, die von der JPA-Implementierung unterstützt wird.

Wenn wir uns die Klasse genau ansehen, werden wir außerdem erkennen, wie die Verwendung von Composition and Dependency Injection es uns ermöglicht, nur die für unsere Anwendung erforderlichen Entity Manager-Methoden aufzurufen.

Einfach ausgedrückt, wir haben eine domänenspezifisch zugeschnittene API und nicht die API des gesamten Entitätsmanagers.

3.2. Refactoring die Benutzerklasse

In diesem Fall verwenden wir Hibernate als JPA-Standardimplementierung und überarbeiten daher die Benutzerklasse entsprechend:

@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. Programmgesteuertes Bootstrapping eines JPA Entity Managers

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.

Der wichtigste Punkt, der hier hervorgehoben werden muss, ist, wie die JpaUserDao- Klasse dazu beiträgt, dass die UserApplication- Klasse völlig unabhängig davon bleibt, wie die Persistenzschicht CRUD-Operationen ausführt .

Darüber hinaus könnten wir MySQL später für jedes andere RDBMS (und sogar für eine flache Datenbank) austauschen, und dennoch würde unsere Anwendung dank der Abstraktionsebene, die von der Dao- Schnittstelle und dem Entitätsmanager bereitgestellt wird, wie erwartet weiter funktionieren .

4. Fazit

In diesem Artikel haben wir uns eingehend mit den Schlüsselkonzepten des DAO-Musters befasst, wie es in Java implementiert wird und wie es zusätzlich zum Entitätsmanager von JPA verwendet wird.

Wie üblich sind alle in diesem Artikel gezeigten Codebeispiele auf GitHub verfügbar.