1. 概述

在实际开发中,尤其是数据驱动型应用里,DAO 和 Repository 的实现常常被混用,甚至当成可以互换的组件。这导致很多开发者对两者的边界感到困惑。

本文将深入剖析 DAO 模式Repository 模式 的本质区别,帮你理清什么时候该用哪个,避免在架构设计上“踩坑”。


2. DAO 模式

DAO(Data Access Object)模式,中文叫 数据访问对象,它的核心是对数据持久化的抽象,并且通常更贴近底层存储结构,比如数据库表。

✅ 通俗点说:DAO 就是数据库表的“代言人”,它负责把 Java 对象和数据库记录之间做映射,屏蔽掉 SQL 等底层细节。

我们来看一个简单的例子。

2.1. User 实体类

先定义一个基本的 User 领域对象:

public class User {
    private Long id;
    private String userName;
    private String firstName;
    private String email;

    // getters and setters
}

2.2. UserDao 接口

接着定义 UserDao 接口,提供标准的 CRUD 操作:

public interface UserDao {
    void create(User user);
    User read(Long id);
    void update(User user);
    void delete(String userName);
}

⚠️ 注意:这里的 delete 是根据 userName 删除,不是按主键,说明 DAO 的操作可以非常灵活,贴近实际表结构。

2.3. UserDaoImpl 实现类

最后是实现类,使用 JPA 的 EntityManager 来操作数据库:

public class UserDaoImpl implements UserDao {
    private final EntityManager entityManager;
    
    @Override
    public void create(User user) {
        entityManager.persist(user);
    }

    @Override
    public User read(Long id) {
        return entityManager.find(User.class, id);
    }

    @Override
    public void update(User user) {
        entityManager.merge(user);
    }

    @Override
    public void delete(String userName) {
        // 构造查询并执行删除
        entityManager.createQuery("DELETE FROM User u WHERE u.userName = :userName")
                     .setParameter("userName", userName)
                     .executeUpdate();
    }
}

📌 这里我们用 EntityManager 直接和数据库交互,DAO 的职责非常明确:只管数据怎么存、怎么取


3. Repository 模式

Repository 模式的定义来自 DDD(领域驱动设计)鼻祖 Eric Evans 的著作:

“Repository 是一种封装了存储、检索和查询行为的机制,它模拟了一个对象集合。”

简单粗暴理解:Repository 看起来就像一个内存中的集合,你可以 addgetremove,而不用关心背后是查数据库、调 API 还是读文件。

根据《企业应用架构模式》的描述,Repository 的作用是:

“在领域层和数据映射层之间做协调,提供类似集合的接口来访问领域对象。”

所以,Repository 的定位比 DAO 更高,更贴近业务逻辑,而不是数据库结构。

它通常会调用一个或多个 DAO 来组装数据,最终返回一个完整的领域对象。

3.1. UserRepository 接口

我们定义一个 UserRepository 接口:

public interface UserRepository {
    User get(Long id);
    void add(User user);
    void update(User user);
    void remove(User user);
}

看起来和 DAO 很像?别急,关键在实现。

3.2. UserRepositoryImpl 实现类

public class UserRepositoryImpl implements UserRepository {
    private UserDaoImpl userDaoImpl;
    
    @Override
    public User get(Long id) {
        return userDaoImpl.read(id);
    }

    @Override
    public void add(User user) {
        userDaoImpl.create(user);
    }

    @Override
    public void update(User user) {
        userDaoImpl.update(user);
    }

    @Override
    public void remove(User user) {
        userDaoImpl.delete(user.getUserName());
    }
}

乍一看,这不就是套了个壳?
✅ 没错,如果领域对象是“贫血”的(只有 getter/setter,没有行为),那 Repository 确实和 DAO 差不多。

但它的真正价值体现在复杂业务场景中。


4. Repository 模式结合多个 DAO

为了体现 Repository 的优势,我们来模拟一个真实业务场景:

用户登录后,要展示他的社交资料,包括 Twitter 推文、Facebook 帖子等。

这时候,单靠一个 User 表远远不够,需要聚合多个数据源。

4.1. Tweet 类

先定义推文实体:

public class Tweet {
    private String email;
    private String tweetText;    
    private Date dateCreated;

    // getters and setters
}

4.2. TweetDao 与 TweetDaoImpl

定义 DAO 接口:

public interface TweetDao {
    List<Tweet> fetchTweets(String email);    
}

实现类中调用第三方 API(比如 Twitter)获取数据:

public class TweetDaoImpl implements TweetDao {
    @Override
    public List<Tweet> fetchTweets(String email) {
        List<Tweet> tweets = new ArrayList<>();

        // 调用 Twitter API,根据 email 查询用户推文
        // mock: 实际项目中会通过 HTTP 客户端调用 REST API
        Tweet tweet = new Tweet();
        tweet.setEmail(email);
        tweet.setTweetText("Hello, this is my first tweet!");
        tweet.setDateCreated(new Date());
        tweets.add(tweet);

        return tweets;
    }
}

⚠️ 注意:这里的 DAO 不再操作数据库,而是封装了对外部服务的调用。DAO 的本质是“数据访问”,不局限于数据库

4.3. 扩展 User 领域模型

我们创建一个增强版的 UserSocialMedia 类:

public class UserSocialMedia extends User {
    private List<Tweet> tweets;

    // getters and setters
}

这个类代表了一个完整的业务概念:带社交内容的用户资料

4.4. 升级 UserRepositoryImpl

现在,Repository 的价值体现出来了:

public class UserRepositoryImpl implements UserRepository {
    private UserDaoImpl userDaoImpl;
    private TweetDaoImpl tweetDaoImpl;
    
    @Override
    public User get(Long id) {
        // 1. 从数据库加载用户基本信息
        UserSocialMedia user = (UserSocialMedia) userDaoImpl.read(id);
        
        // 2. 从 Twitter 获取推文
        List<Tweet> tweets = tweetDaoImpl.fetchTweets(user.getEmail());
        user.setTweets(tweets);
        
        // 3. 返回完整的业务对象
        return user;
    }

    // 其他方法略...
}

✅ 关键点:

  • Repository 协调了多个 DAO(UserDaoImplTweetDaoImpl
  • 聚合来自不同数据源的信息
  • 返回一个完整的、可直接用于业务逻辑的领域对象

这就是 Repository 的核心价值:为业务服务,而不是为数据库服务


5. 两种模式对比总结

特性 DAO 模式 Repository 模式
定位 数据访问层,贴近存储 领域层,贴近业务逻辑
抽象目标 数据库表或数据源 对象集合(领域对象)
层级 较低,靠近数据库 较高,靠近业务
依赖关系 不依赖 Repository 可以依赖一个或多个 DAO
职责 执行 CRUD,隐藏 SQL 聚合数据,构建完整领域对象
适用场景 简单 CRUD、数据驱动应用 复杂业务逻辑、领域驱动设计

📌 核心结论:

  • DAO 是基础设施,Repository 是业务抽象
  • Repository 可以组合多个 DAO,但 DAO 不该依赖 Repository
  • ✅ 如果你的领域模型是贫血的(anemic),那 Repository 很可能只是 DAO 的马甲
  • ✅ 真正的 Repository 应该体现领域逻辑,比如 findActiveUsersWithPremiumSubscription()

6. 结论

DAO 和 Repository 并不是非此即彼的选择,而是不同层次的抽象工具

  • 当你的应用是简单的 CRUD 时,用 DAO 完全够用,简单直接。
  • 但当你开始做领域驱动设计、业务逻辑变复杂时,Repository 才真正发挥价值

📌 一句话总结:

DAO 关心“数据怎么存”,Repository 关心“业务需要什么数据”

合理使用两者,才能让代码既灵活又可维护。

所有示例代码已上传至 GitHub:https://github.com/baeldung/design-patterns-architectural


原始标题:DAO vs Repository Patterns | Baeldung