The DAO Pattern in Java – Java中的DAO模式

最后修改: 2018年 6月 28日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The Data Access Object (DAO) pattern is a structural pattern that allows us to isolate the application/business layer from the persistence layer (usually a relational database but could be any other persistence mechanism) using an abstract API.

数据访问对象(DAO)模式是一种结构模式,它允许我们使用抽象的API将应用/业务层与持久化层(通常是关系型数据库,但也可以是任何其他持久化机制)隔离开来。

The API hides from the application all the complexity of performing CRUD operations in the underlying storage mechanism. This permits both layers to evolve separately without knowing anything about each other.

API向应用程序隐藏了在底层存储机制中执行CRUD操作的所有复杂性。这允许两层分别发展,而不知道对方的任何情况。

In this tutorial, we’ll take a deep dive into the pattern’s implementation, and we’ll learn how to use it for abstracting calls to a JPA entity manager.

在本教程中,我们将深入了解该模式的实现,并学习如何将其用于抽象调用JPA实体管理器

2. A Simple Implementation

2.一个简单的实现

To understand how the DAO pattern works, let’s create a basic example.

为了理解DAO模式是如何工作的,让我们创建一个基本的例子。

Let’s say that we want to develop an application that manages users. We want to keep the application’s domain model completely agnostic about the database. So, we’ll create a simple DAO class that will take care of keeping these components neatly decoupled from each other.

假设我们想开发一个管理用户的应用程序。我们希望保持该应用程序的领域模型与数据库完全无关。因此,我们将创建一个简单的DAO类,它将负责保持这些组件之间整齐的解耦。

2.1. The Domain Class

2.1.域类

As our application will work with users, we need to define just one class for implementing its domain model:

由于我们的应用程序将与用户一起工作,我们只需要定义一个类来实现其领域模型。

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

The User class is just a plain container for user data, so it doesn’t implement any other behavior worth stressing.

User类只是一个普通的用户数据容器,所以它并没有实现任何其他值得强调的行为。

Of course, the important design choice here is how to keep the application using this class isolated from any persistence mechanism that could be implemented.

当然,这里重要的设计选择是如何让使用这个类的应用程序与任何可能实现的持久化机制隔离开来。

And that’s exactly the issue that the DAO pattern attempts to address.

而这正是DAO模式试图解决的问题。

2.2. The DAO API

2.2.DAO的API

Let’s define a basic DAO layer so we can see how it can keep the domain model completely decoupled from the persistence layer.

让我们来定义一个基本的DAO层,这样我们就可以看到它如何保持领域模型与持久化层完全解耦。

Here’s the DAO API:

这里是DAO的API。

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

From a bird’s-eye view, it’s clear that the Dao interface defines an abstract API that performs CRUD operations on objects of type T.

从鸟瞰来看,很明显,Dao接口定义了一个抽象的API,对T类型的对象执行CRUD操作。

Due to the high level of abstraction that the interface provides, it’s easy to create a concrete, fine-grained implementation that works with User objects.

由于该接口提供了高度的抽象性,因此很容易创建一个具体的、细粒度的实现,该实现可与User对象一起工作。

2.3. The UserDao Class

2.3.UserDao

Let’s define a user-specific implementation of the Dao interface:

让我们来定义一个用户专用的Dao接口的实现。

public class UserDao implements Dao<User> {
    
    private List<User> users = new ArrayList<>();
    
    public UserDao() {
        users.add(new User("John", "john@domain.com"));
        users.add(new User("Susan", "susan@domain.com"));
    }
    
    @Override
    public Optional<User> get(long id) {
        return Optional.ofNullable(users.get((int) id));
    }
    
    @Override
    public List<User> 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);
    }
}

The UserDao class implements all the functionality required for fetching, updating and removing User objects.

UserDao类实现了获取、更新和删除User对象所需的所有功能。

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.

为了简单起见,用户列表就像一个内存数据库,它在构造函数中填充了几个用户对象。

Of course, it’s easy to refactor the other methods so they can work, for instance, with a relational database.

当然,重构其他方法是很容易的,这样它们就可以工作,例如,与关系型数据库一起工作。

While both the User and UserDao classes coexist independently within the same application, we still need to see how the latter can be used for keeping the persistence layer hidden from application logic:

虽然UserUserDao类在同一个应用程序中独立共存,但我们仍然需要看到后者如何用于保持持久化层与应用逻辑的隐藏。

public class UserApplication {

    private static Dao<User> userDao;

    public static void main(String[] args) {
        userDao = new UserDao();
        
        User user1 = getUser(0);
        System.out.println(user1);
        userDao.update(user1, new String[]{"Jake", "jake@domain.com"});
        
        User user2 = getUser(1);
        userDao.delete(user2);
        userDao.save(new User("Julie", "julie@domain.com"));
        
        userDao.getAll().forEach(user -> System.out.println(user.getName()));
    }

    private static User getUser(long id) {
        Optional<User> user = userDao.get(id);
        
        return user.orElseGet(
          () -> new User("non-existing user", "no-email"));
    }
}

The example is contrived, but it shows in a nutshell the motivations behind the DAO pattern. In this case, the main method just uses a UserDao instance to perform CRUD operations on a few User objects.

这个例子是设计好的,但它简要地展示了DAO模式背后的动机。在这个例子中,main方法只是使用一个UserDao实例来对一些User对象执行CRUD操作。

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.

这个过程中最相关的方面是UserDao如何向应用程序隐藏所有关于对象如何被持久化、更新和删除的低级细节。

3. Using the Pattern With JPA

3.在JPA中使用该模式

There’s a tendency among developers to think that the release of JPA downgraded to zero the DAO pattern’s functionality. The pattern becomes just another layer of abstraction and complexity on top of the one provided by JPA’s entity manager.

在开发者中,有一种倾向,认为JPA的发布将DAO模式的功能降级为零。该模式只是在JPA的实体管理器提供的抽象和复杂性之上的另一层。

This is true in some scenarios. Even so, we sometimes just want to expose to our application only a few domain-specific methods of the entity manager’s API. The DAO pattern has its place in such cases.

这在某些情况下是真的。即便如此,我们有时只想向我们的应用程序公开实体管理器API的几个特定领域的方法。在这种情况下,DAO模式有其用武之地。

3.1. The JpaUserDao Class

3.1.JpaUserDao

Let’s create a new implementation of the Dao interface to see how it can encapsulate the functionality that JPA’s entity manager provides out of the box:

让我们创建一个新的Dao接口的实现,看看它如何封装JPA的实体管理器所提供的功能。

public class JpaUserDao implements Dao<User> {
    
    private EntityManager entityManager;
    
    // standard constructors
    
    @Override
    public Optional<User> get(long id) {
        return Optional.ofNullable(entityManager.find(User.class, id));
    }
    
    @Override
    public List<User> 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<EntityManager> action) {
        EntityTransaction tx = entityManager.getTransaction();
        try {
            tx.begin();
            action.accept(entityManager);
            tx.commit(); 
        }
        catch (RuntimeException e) {
            tx.rollback();
            throw e;
        }
    }
}

The JpaUserDao class can work with any relational database supported by the JPA implementation.

JpaUserDao类可以与JPA实现所支持的任何关系型数据库一起工作。

Also, if we look closely at the class, we’ll realize how the use of Composition and Dependency Injection allows us to call only the entity manager methods required by our application.

另外,如果我们仔细观察这个类,我们会发现,使用组合依赖注入,可以让我们只调用应用程序所需的实体管理器方法。

Simply put, we have a domain-specific tailored API, rather than the entire entity manager’s API.

简单地说,我们有一个针对特定领域的定制API,而不是整个实体管理器的API。

3.2. Refactoring the User Class

3.2.重构User

In this case, we’ll use Hibernate as the JPA default implementation, so we’ll refactor the User class accordingly:

在这种情况下,我们将使用Hibernate作为JPA的默认实现,所以我们将相应地重构User类。

@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. Bootstrapping a JPA Entity Manager Programmatically

3.3.引导JPA实体管理器程序化

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.

假设我们已经有一个在本地或远程运行的MySQL实例,并且有一个数据库表“users”填充了一些用户记录,我们需要获得一个JPA实体管理器,以便我们可以使用JpaUserDao类在数据库中执行CRUD操作。

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

在大多数情况下,我们通过典型的persistence.xml文件来实现,这是标准的方法。

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

在这种情况下,我们将采取无XML的方法,通过Hibernate方便的EntityManagerFactoryBuilderImpl类,用纯Java获得实体管理器。

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

关于如何用Java引导JPA实现的详细解释,请查看这篇文章

3.4. The UserApplication Class

3.4.UserApplication

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

最后,让我们重构初始的UserApplication类,使其能够与JpaUserDao实例一起工作,并对User实体运行CRUD操作。

public class UserApplication {

    private static Dao<User> jpaUserDao;

    // standard constructors
    
    public static void main(String[] args) {
        User user1 = getUser(1);
        System.out.println(user1);
        updateUser(user1, new String[]{"Jake", "jake@domain.com"});
        saveUser(new User("Monica", "monica@domain.com"));
        deleteUser(getUser(2));
        getAllUsers().forEach(user -> System.out.println(user.getName()));
    }
    
    public static User getUser(long id) {
        Optional<User> user = jpaUserDao.get(id);
        
        return user.orElseGet(
          () -> new User("non-existing user", "no-email"));
    }
    
    public static List<User> 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);
    }
}

The example here is pretty limited. But it’s useful for showing how to integrate the DAO pattern’s functionality with the one that the entity manager provides.

这里的例子是相当有限的。但它对于展示如何将DAO模式的功能与实体管理器提供的功能结合起来是很有用的。

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.

在大多数应用程序中,有一个DI框架,它负责将一个JpaUserDao实例注入到UserApplication类。为了简单起见,我们省略了这个过程的细节。

The most relevant point to stress here is how the JpaUserDao class helps to keep the UserApplication class completely agnostic about how the persistence layer performs CRUD operations.

这里要强调的最相关的一点是,JpaUserDao类如何帮助保持UserApplication类对持久层如何执行CRUD操作完全不了解。

In addition, we could swap MySQL for any other RDBMS (and even for a flat database) further down the road, and our application would continue working as expected, thanks to the level of abstraction provided by the Dao interface and the entity manager.

此外,由于Dao接口和实体管理器所提供的抽象水平,我们可以将MySQL换成任何其他RDBMS(甚至是扁平数据库),而我们的应用程序将继续按预期工作。

4. Conclusion

4.结论

In this article, we took an in-depth look at the DAO pattern’s key concepts. We saw how to implement it in Java and how to use it on top of JPA’s entity manager.

在这篇文章中,我们深入了解了DAO模式的关键概念。我们看到了如何在Java中实现它以及如何在JPA的实体管理器之上使用它。

As usual, all the code samples shown in this article are available over on GitHub.

像往常一样,本文中显示的所有代码样本都可以在GitHub上找到