理清楚 Dao、Service、Controller

发布时间 2023-09-07 14:37:49作者: Himmelbleu

三层架构的意义

还记得我当初学 Java 操作数据库的示例代码时要做的步骤:

  1. 连接数据库
  2. 编译 sql
  3. 执行 sql,获取结果集
  4. 遍历结果集,封装对象
  5. 得到的结果(或集合)返回出去
  6. 在 Servlet 中调用写好的函数,将结果转换成 JSON 字符串返回给前端
public class JdbcMySql {

    private String url = "jdbc:mysql://127.0.0.1:3306/test";

    private String username = "root";

    private String password = "123456";

    private String driver = "com.mysql.jdbc.Driver";

    private Connection connection;

    // 编译 sql 语句的
    private Statement statement;


    public JdbcMySql() {
        try {
            // 1. 构造函数最先执行,开始寻找 mysql 的驱动
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public void open() {
        // 2. 执行 open 函数,打开连接,连接 mysql 数据库
        try {
            connection = DriverManager.getConnection(url, username, password);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    // 3. 打开通道之后,我们就要执行数据库操作了
    // crud

    // 查询数据库 user 表
    public List<User> query() {
        List<User> list = new ArrayList<>();

        try {
            // 1. 获取编译 sql 的对象
            statement = connection.createStatement();
            String sql = "select * from t_users";
            // 2. 执行 sql,得到结果
            ResultSet resultSet = statement.executeQuery(sql);

            // 3. 循环遍历结果
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("username");
                String pwd = resultSet.getString("password");
                
                // 4. 封装数据为对象
                User user = new User();
                user.setId(id);
                user.setUsername(name);
                user.setPassword(pwd);
                
                // 5. 添加到集合中
                list.add(user);
            }
            // 6. 得到的集合返回出去
            return list;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

在写 query 函数时,我们并没有业务在里面,所谓业务就是一堆判断、不合法的获取等等。当涉及到了业务的时候,这个 query 函数代码就变得非常多,通常写一个业务会存在多个步骤,当所有步骤都写在一个函数里面,代码的阅读性就变得非常地低了。

所以,我们上面的代码可以拆分步骤,再将这些步骤组装到函数里面。

add:[private List<User> extractUserDataFromSet(ResultSet set) throws SQLException {
    List<User> list = new ArrayList<>();

    while (set.next()) {
        int id = set.getInt("id");
        String name = set.getString("username");
        String pwd = set.getString("password");

        User user = new User();
        user.setId(id);
        user.setUsername(name);
        user.setPassword(pwd);

        list.add(user);
    }

    return list;
}]:add

public List<User> query() {
    try {
        statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery("select * from t_users");

        lit:[return extractUserDataFromSet(resultSet);]:lit
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

阅读函数名称 extractUserDataFromSet,就可以知道这个函数是专门做从结果集中抽取用户数据的。这样可以让我们一目了然,这个函数在做什么。当有其他处理步骤时,再写一个简明扼要的函数名称,去专门做这个事情。

tip:[start]

虽然这个函数抽离出来之后可能从始至终使用一次,但是可以提升我们代码的可阅读性和维护性。注意,这种函数的抽离并不会降低代码的耦合度。

tip:[end]

假如,有一个非常简单的业务处理:

public List<User> query() {
    try {
        statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery("select * from t_users");

        List<User> list = extractUserDataFromSet(resultSet);

        add:[if (list.isEmpty()) {
            System.out.println("查询不到数据");
            return null;
        } else {
            return list;
        }]:add
        
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

如果这个业务非常复杂,代码也很多,不可能一直在这个类继续添加函数去处理,随着功能增加,我们的这个类就显得非常冗余、混乱。

所以,将代码分层书写就变得非常重要!