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
:删除对象
接口的高度抽象性,使得我们可以轻松为不同实体(如 User
、Order
)提供具体实现。
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