Das DAO-Muster in Java

Das DAO-Muster in Java

1. Überblick

Das DAO-Muster (Data Access Object) ist ein Strukturmuster, das es uns ermöglicht,isolate the application/business layer from the persistence layer (usually a relational database, but it could be any other persistence mechanism) using an abstract API.

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 unabhängig voneinander entwickeln, ohne voneinander etwas zu wissen.

In diesem Tutorial werden wir uns eingehend mit der Implementierung des Musters befassen und lernen, wie Sie es zum Abstrahieren von Aufrufen anJPA entity manager verwenden.

Weitere Lektüre:

Einführung in Spring Data JPA

Einführung in Spring Data JPA mit Spring 4 - die Spring-Konfiguration, das DAO, manuelle und generierte Abfragen und das Transaktionsmanagement.

Read more

Übersicht über JPA / Hibernate-Kaskadentypen

Ein schneller und praktischer Überblick über JPA / Hibernate Cascade Types.

Read more

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 vollständig unabhängig zu halten, erstellen wira simple DAO class that will take care of keeping these components neatly decoupled from each other.

2.1. Die Domain-Klasse

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 KlasseUserist nur ein einfacher Container für Benutzerdaten, sodass kein anderes Verhalten implementiert wird, das es wert ist, betont zu werden.

Natürlich ist die wichtigste Entwurfsentscheidung, die wir hier treffen müssen, wie die Anwendung, die diese Klasse verwendet, von jedem Persistenzmechanismus isoliert bleibt, der irgendwann implementiert werden könnte.

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

2.2. Die DAO-API

Definieren wir eine grundlegende DAO-Ebene, damit wir sehen können, wie siekeep the domain model completely decoupled from the persistence layer. 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 dieDao-Schnittstelle eine abstrakte API definiert, die CRUD-Operationen für Objekte vom TypT ausführt.

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

2.3. DieUserDao Klasse

Definieren wir eine benutzerspezifische Implementierung derDao-Schnittstelle:

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 KlasseUserDaoimplementiert alle Funktionen, die zum Abrufen, Aktualisieren und Entfernen der Objekte vonUsererforderlich sind.

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.

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

Während sowohl die KlassenUser als auchUserDao unabhängig voneinander in derselben Anwendung existieren, müssen wir noch sehen, wie letztere verwendet werden können, um die Persistenzschicht vor der Anwendungslogik verborgen zu halten:

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 kurz und bündig die Beweggründe hinter dem DAO-Muster. In diesem Fall verwendet die Methodemainnur eine Instanz vonUserDao, um CRUD-Operationen für einigeUser-Objekte auszuführen.

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

Zweifellos ist dies in einigen Szenarien der Fall. Trotzdem, sometimes we just want to expose to our application only a few domain-specific methods of the entity manager’s API. In solchen Fällen hat das DAO-Muster seinen Platz.

3.1. DieJpaUserDao Klasse

Nachdem dies gesagt ist, erstellen wir eine neue Implementierung derDao-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 KlasseJpaUserDaokann 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 vonComposition undDependency Injection es uns ermöglicht, nur die für unsere Anwendung erforderlichen Entity-Manager-Methoden aufzurufen.

Einfach ausgedrückt, wir haben eine domänenspezifische, maßgeschneiderte API anstelle der API des gesamten Entitätsmanagers.

3.2. Refactoring derUser-Klasse

In diesem Fall verwenden wir Hibernate als JPA-Standardimplementierung. Daher werden wir die KlasseUserentsprechend umgestalten:

@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

Angenommen, wir haben bereits eine funktionierende Instanz von MySQL, die entweder lokal oder remote ausgeführt wird, und eine Datenbanktabelle“users”, die mit einigen Benutzerdatensätzen gefüllt ist, benötigen wir einen JPA-Entitätsmanager, damit wir die KlasseJpaUserDaoverwenden können zum Ausführen von CRUD-Operationen in der Datenbank.

In den meisten Fällen erreichen wir dies über die typische“persistence.xml”-Datei, die der Standardansatz ist.

In diesem Fall verfolgen wir einen“xml-less”-Ansatz und erhalten den Entitätsmanager mit einfachem Java über die praktischeEntityManagerFactoryBuilderImpl-Klasse von Hibernate.

Eine ausführliche Erklärung zum Booten einer JPA-Implementierung mit Java finden Sie unterthis article.

3.4. DieUserApplication Klasse

Lassen Sie uns abschließend die anfänglicheUserApplication-Klasse umgestalten, damit sie mit einerJpaUserDao-Instanz arbeiten und CRUD-Operationen für dieUser-Entitäten ausführen kann:

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

Auch wenn das Beispiel in der Tat ziemlich begrenzt ist, bleibt es nützlich, um zu demonstrieren, wie die Funktionalität des DAO-Musters in die vom Entitätsmanager bereitgestellte integriert werden kann.

In den meisten Anwendungen gibt es ein DI-Framework, das für das Einfügen einerJpaUserDao-Instanz in dieUserApplication-S-Klasse verantwortlich ist. Der Einfachheit halber haben wir die Details dieses Prozesses weggelassen.

Der wichtigste Punkt, um hier zu betonen, ist, wiethe JpaUserDao class helps to keep the UserApplication class completely agnostic about how the persistence layer performs CRUD operations.

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 derDao-Schnittstelle bereitgestellt wird, wie erwartet weiterarbeiten und der Entity Manager.

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 Codebeispieleover on GitHub verfügbar.