1. 概述

数据访问对象(Data Access Object,简称 DAO)模式是一种结构型设计模式,它的核心目标是通过抽象接口,将应用层或业务逻辑层与持久化层(通常是关系型数据库,但也可能是其他存储机制)解耦

这个接口屏蔽了底层存储机制中执行 CRUD 操作的所有复杂性,使得上下两层可以独立演进,互不感知。换句话说,业务代码不需要关心数据是怎么存的,数据库怎么换的,只要调用 DAO 提供的方法就行。

本文会深入剖析 DAO 模式在 Java 中的实现方式,并结合 JPA(Java Persistence API)实体管理器(EntityManager)的使用场景,展示其在现代开发中的价值。

关键价值:解耦 + 可测试性 + 易于替换持久化实现


2. 一个简单的实现

为了理解 DAO 模式的工作原理,我们从一个最基础的用户管理场景入手。

假设我们要开发一个用户管理系统,核心诉求是:让业务模型完全不知道数据库的存在。这时,DAO 就是那个“中间人”。

2.1. 领域类(Domain Class)

先定义一个纯粹的数据载体类 User

public class User {
    
    private String name;
    private String email;
    
    // 构造方法、getter、setter 省略
}

这个类就是典型的 POJO(Plain Old Java Object),不掺杂任何持久化逻辑。它的存在只是为了承载数据,干净、简单、可复用。

⚠️ 踩坑提醒:别把业务逻辑或数据库字段注解塞进领域类,否则就违背了“解耦”初衷。


2.2. DAO 接口定义

接下来定义一个通用的 DAO 接口,作为所有数据访问操作的抽象:

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

这个接口定义了对泛型类型 T 的基本 CRUD 操作:

  • get:按 ID 查询,返回 Optional 避免空指针
  • getAll:获取全部记录
  • save:保存新对象
  • update:更新对象(这里用字符串数组传参仅作演示,实际项目建议用 DTO 或对象)
  • delete:删除对象

接口的高度抽象性,使得我们可以轻松为不同实体(如 UserOrder)提供具体实现。


2.3. UserDao 实现类

现在实现一个针对 User 的 DAO:

public class UserDao implements Dao<User> {
    
    private List<User> users = new ArrayList<>();
    
    public UserDao() {
        users.add(new User("John", "john@example.com"));
        users.add(new User("Susan", "susan@example.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);
    }
}

📌 关键点说明

  • users 列表充当了内存数据库,仅用于演示。
  • ✅ 所有操作都通过 UserDao 完成,调用方无需知道数据存在哪。
  • update 方法设计粗糙(用 String[] 传参),实际项目中应避免,建议使用专门的更新参数对象。

2.4. 应用层调用示例

最后看业务层如何使用 UserDao

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@example.com"});
        
        User user2 = getUser(1);
        userDao.delete(user2);
        userDao.save(new User("Julie", "julie@example.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"));
    }
}

虽然例子很简单,但已经体现了 DAO 的精髓:

业务代码只依赖抽象接口 Dao<User>,完全不知道数据是存在内存、MySQL 还是 Redis。


3. 结合 JPA 使用 DAO 模式

有些开发者认为,JPA 出现后 DAO 模式已经过时了——毕竟 EntityManager 本身就是一个高度抽象的接口。再加一层 DAO,是不是画蛇添足?

不一定。

在实际项目中,我们往往不希望业务层直接调用 EntityManager 的全部方法。我们只想暴露少量、领域相关的操作。这时,DAO 依然是非常有价值的封装层。


3.1. JpaUserDao 实现

我们来实现一个基于 JPA 的 UserDao

public class JpaUserDao implements Dao<User> {
    
    private EntityManager entityManager;
    
    // 构造方法注入 EntityManager
    public JpaUserDao(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
    
    @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;
        }
    }
}

📌 亮点分析

  • ✅ 使用 组合(Composition)依赖注入(DI)EntityManager 作为依赖被注入。
  • ✅ 所有数据库操作都包裹在事务中,通过 executeInsideTransaction 统一管理。
  • ✅ 业务层依然只调用 Dao<User> 接口,完全感知不到 JPA 的存在
  • ✅ 即使未来换成 MyBatis 或 Spring Data JPA,只要实现同一个接口,上层代码无需修改。

3.2. User 实体类(JPA 注解版)

为了让 User 能被 JPA 管理,需添加 JPA 注解:

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    
    private String name;
    private String email;
    
    // 构造方法、getter、setter 省略
}

📌 注解说明:

  • @Entity:标记为 JPA 实体
  • @Table:指定数据库表名
  • @Id + @GeneratedValue:主键自增策略

3.3. 程序化初始化 JPA EntityManager(无 XML)

传统方式通过 persistence.xml 配置 JPA,但我们更推荐代码方式初始化,尤其是在 Spring Boot 之外的场景。

这里以 Hibernate 为例,使用 EntityManagerFactoryBuilderImpl 手动构建:

// 示例:手动创建 EntityManager
private static EntityManager createEntityManager() {
    Map<String, String> props = new HashMap<>();
    props.put("javax.persistence.jdbc.url", "jdbc:mysql://localhost:3306/testdb");
    props.put("javax.persistence.jdbc.user", "root");
    props.put("javax.persistence.jdbc.password", "password");
    props.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
    props.put("hibernate.hbm2ddl.auto", "update");

    EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistence", props);
    return emf.createEntityManager();
}

优势:配置灵活,适合动态环境或测试场景。
⚠️ 生产环境建议结合 DI 框架(如 Spring)管理生命周期。


3.4. 更新后的 UserApplication

public class UserApplication {

    private static Dao<User> jpaUserDao;

    public static void main(String[] args) {
        EntityManager em = createEntityManager();
        jpaUserDao = new JpaUserDao(em);

        User user1 = getUser(1);
        System.out.println(user1);
        updateUser(user1, new String[]{"Jake", "jake@example.com"});
        saveUser(new User("Monica", "monica@example.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);
    }
}

📌 核心价值再次强调

UserApplication 依然不知道数据是存在 MySQL、PostgreSQL 还是 H2 内存库
✅ 只要 Dao<User> 接口不变,底层换数据库、换 ORM 框架,上层业务代码零修改


4. 总结

DAO 模式在现代 Java 开发中依然有其不可替代的价值,尤其是在以下场景:

  • ✅ 需要对 EntityManager 或其他 ORM 接口进行细粒度封装
  • ✅ 希望业务层与持久化技术彻底解耦
  • ✅ 项目需要支持多种存储后端(如 MySQL + MongoDB)
  • ✅ 提升单元测试便利性(可轻松 Mock DAO)

虽然 Spring Data JPA 等框架已经提供了 Repository 抽象,但在复杂项目中,自定义 DAO 依然是控制访问边界、提升代码可维护性的有效手段。

💡 一句话总结:DAO 不是过时的技术,而是“用抽象隔离变化”这一设计原则的经典实践。

所有示例代码已托管至 GitHub:https://github.com/baeldung/dao-pattern-example


原始标题:The DAO Pattern in Java | Baeldung