重构第65章:重构与数据库访问——引入Repository模式

说实话,很多项目的数据库访问层,最后都变成了“大泥球”。

业务逻辑里直接塞SQL,查询散落在各个Controller里,想换个ORM?那简直是噩梦。我见过一个项目,光改个数据库连接池,就改了三天——因为SQL写得太“野”了。

这一章,我们来聊聊怎么用Repository模式,把数据访问层从“野路子”拉回正轨。

为什么需要Repository模式?

先问个问题:你的业务代码里,是不是经常出现这样的代码?

// 业务逻辑里直接写SQL
public Order getOrder(Long id) {
    String sql = "SELECT * FROM orders WHERE id = ?";
    return jdbcTemplate.queryForObject(sql, new Object[]{id}, orderRowMapper);
}

这有什么问题?

  • 耦合太深:业务逻辑和数据库实现绑死了。
  • 测试困难:想单元测试?得先启动数据库。
  • 切换成本高:从MySQL换到PostgreSQL?所有SQL都得改。

我个人习惯,把数据访问抽象成接口。业务层只跟接口打交道,底层实现随便换。这就是Repository模式的核心思想。

核心定义:Repository模式在领域层和数据映射层之间建立了一个中间层。它让业务代码感觉“就像在操作一个内存中的集合”,而不是在操作数据库。

重构前的“大泥球”

先看一个典型的“坏味道”代码。假设我们有个用户管理系统,业务逻辑和数据库访问混在一起:

public class UserService {
    private JdbcTemplate jdbcTemplate;

    public User findUserById(Long id) {
        // 直接写SQL
        String sql = "SELECT id, name, email FROM users WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{id}, (rs, rowNum) -> {
            User user = new User();
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
            user.setEmail(rs.getString("email"));
            return user;
        });
    }

    public void saveUser(User user) {
        String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
        jdbcTemplate.update(sql, user.getName(), user.getEmail());
    }

    // 还有更多方法... 全都混在一起
}

嗯,这里要注意。这个类看起来“功能完整”,但一旦需求变复杂,它就会迅速膨胀。我在项目中遇到过类似的情况——一个Service类最后写了3000行,里面一半是SQL,一半是业务逻辑。改一个查询,得从头读到尾,生怕改错。

引入Repository模式:三步走

重构其实不复杂。分三步走:

  1. 定义Repository接口:抽象出数据访问的契约。
  2. 实现Repository接口:把具体的SQL实现藏起来。
  3. 修改业务层:让业务代码只依赖接口。

第一步:定义接口

public interface UserRepository {
    User findById(Long id);
    void save(User user);
    void deleteById(Long id);
    List<User> findAll();
}

你看,这个接口里没有任何SQL的影子。业务层看到的就是“我要找用户”、“我要保存用户”。至于底层是MySQL还是Redis,业务层不关心。

第二步:实现接口

@Repository
public class JdbcUserRepository implements UserRepository {
    private final JdbcTemplate jdbcTemplate;

    public JdbcUserRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public User findById(Long id) {
        String sql = "SELECT id, name, email FROM users WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{id}, this::mapRowToUser);
    }

    @Override
    public void save(User user) {
        String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
        jdbcTemplate.update(sql, user.getName(), user.getEmail());
    }

    // 其他实现...

    private User mapRowToUser(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setName(rs.getString("name"));
        user.setEmail(rs.getString("email"));
        return user;
    }
}

SQL被封装在实现类里。想换ORM?比如换成MyBatis?再写一个MyBatisUserRepository就行。业务层完全不用动。

第三步:改造业务层

public class UserService {
    private final UserRepository userRepository;

    // 依赖注入接口,而不是具体实现
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User getUserById(Long id) {
        return userRepository.findById(id);
    }

    public void registerUser(User user) {
        // 业务逻辑:检查邮箱是否重复
        // 然后调用Repository保存
        userRepository.save(user);
    }
}

业务层干净了。测试也简单了——传一个Mock的UserRepository进去就行。

小技巧:我建议在接口命名上保持一致性。比如都用findByIdsavedelete,而不是有的叫getUser,有的叫queryUser。团队里统一命名规范,能省很多沟通成本。

Repository模式的核心结构

为了让你更直观地理解,我画了一张图:

业务层 UserService Repository接口 UserRepository Repository实现 JdbcUserRepository | MyBatisUserRepository 数据源(MySQL/PostgreSQL/Redis) 业务层只依赖接口,不依赖具体实现。切换数据源只需新增一个实现类。

说白了,Repository模式就是加了一层“中间人”。业务层不直接跟数据库打交道,而是通过这个中间人。中间人负责翻译——把业务层的“我要找用户”翻译成“SELECT * FROM users”。

避坑指南

我曾经踩过一个坑:把Repository做得太“重”。

什么意思呢?就是在一个Repository里塞了太多方法。比如findByNameAndAgefindByNameAndEmailfindByNameAndAgeAndEmail……最后接口膨胀到几十个方法。

怎么避免?

  • 用查询对象(Specification):把查询条件封装成一个对象,Repository只提供一个通用的findBySpecification方法。
  • 分拆Repository:如果一个Repository的方法超过10个,考虑拆分成多个小接口。比如UserQueryRepositoryUserCommandRepository

注意:不要为了用模式而用模式。如果你的项目只有三张表,业务逻辑极其简单,直接用JPA的CrudRepository就够了。过度设计也是坏味道。

什么时候该用?

场景 推荐使用? 原因
项目有复杂的业务逻辑 ✅ 强烈推荐 业务层需要隔离数据访问细节
可能切换数据库或ORM ✅ 强烈推荐 切换成本极低,只需新增实现
需要单元测试 ✅ 推荐 可以轻松Mock Repository
简单的CRUD项目 ⚠️ 谨慎使用 可能过度设计,增加复杂度

你想想看,如果你的项目未来可能要换数据库,或者你的业务逻辑复杂到需要大量单元测试,那Repository模式几乎是必选项。

这一章的内容就到这里。记住:重构不是炫技,是为了让代码更容易维护、更容易测试。Repository模式只是工具,用对了地方,它就是利器。

一句话总结:Repository模式把数据访问抽象成接口,让业务层只关心“做什么”,不关心“怎么做”。

公众号:蓝海资料掘金营,微信deep3321