• JDBC工具类——JdbcUtils(2)


    JDBC工具类——JdbcUtils(2)

    前言

    本系列文章介绍JDBC工具类——JdbcUtils的封装,部分实现参考了Spring框架的JdbcTemplate

    完整项目地址:https://github.com/byx2000/JdbcUtils

    回顾

    在上一篇文章中,我们已经通过提取公共方法将获取连接、语句创建、释放资源这些重复操作抽象成了工具类中的方法。但是,在客户代码中还存在着一些结构上的重复。这些重复,可以通过模板方法模式来进一步封装。

    由于查询操作比较常用,因此我们先考虑对查询操作进行封装。

    抽象ResultSetMapper<T>接口

    在JDBC的查询操作中,需要对结果集进行处理,那么该如何处理呢?这需要让用户程序自己决定。所以,我们抽象出一个ResultSetMapper<T>接口,用于封装用户程序对结果集的处理:

    /**
     * 结果集转换器接口
     * @param <T> 转换类型
     */
    public interface ResultSetMapper<T>
    {
        T map(ResultSet rs) throws Exception;
    }
    

    其中,泛型参数T表示要将结果集转换成什么类型,而接口中的map方法就是具体的转换函数。

    比如说,如果我们想把结果集转换成一个User类的列表,则可以写如下实现类:

    public class UserListResultSetMapper implements ResultSetMapper<List<User>>
    {
        @Override
        public List<User> map(ResultSet rs) throws Exception
        {
            List<User> users = new ArrayList<>();
            while (rs.next())
            {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setUsername(rs.getString("username"));
                user.setPassword(rs.getString("password"));
                users.add(user);
            }
            return users;
        }
    }
    

    封装query函数

    有了ResultSetMapper<T>接口,整个JDBC的查询操作就可以被封装成一个query函数:

    public class JdbcUtils
    {
        ...
        public static <T> T query(String sql, ResultSetMapper<T> resultSetMapper, Object... params)
        {
            ResultSet rs = null;
            PreparedStatement stmt = null;
            Connection conn = null;
            try
            {
                conn = getConnection();
                stmt = createPreparedStatement(conn, sql, params);
                rs = stmt.executeQuery();
                return resultSetMapper.map(rs);
            }
            catch (Exception e)
            {
                throw new RuntimeException(e);
            }
            finally
            {
                close(rs, stmt, conn);
            }
        }
        ...
    }
    

    其中,sqlparams是用户程序传入的sql语句和参数,resultSetMapper是用户程序自定义的结果集处理函数。

    query函数的返回值类型是一个泛型T,该泛型参数就是ResultSetMapper的泛型参数,即ResultSet的转换结果类型。

    query方法内部,调用了之前封装的getConnection方法、close方法和createPreparedStatement方法。

    下面两行代码获取结果集,然后调用用户自定义的结果集转换器转换结果集,最后返回转换结果:

    rs = stmt.executeQuery();
    return resultSetMapper.map(rs);
    

    有了query函数,假设用户需要实现一个“查询所有id大于5的用户”的需求,那么需要写如下代码:

    // UserListResultSetMapper.java
    public class UserListResultSetMapper implements ResultSetMapper<List<User>>
    {
        @Override
        public List<User> map(ResultSet rs) throws Exception
        {
            List<User> users = new ArrayList<>();
            while (rs.next())
            {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setUsername(rs.getString("username"));
                user.setPassword(rs.getString("password"));
                users.add(user);
            }
            return users;
        }
    }
    
    // 查询
    List<User> users = JdbcUtils.query("SELECT * FROM users WHERE id > ?",
                    new UserListResultSetMapper(),
                    5);
    

    如果类UserListResultSetMapper是一次性的,也可以写成匿名内部类的形式:

    List<User> users = JdbcUtils.query("SELECT * FROM users WHERE id > ?", new ResultSetMapper<List<User>>()
    {
        @Override
        public List<User> map(ResultSet rs) throws Exception
        {
            List<User> us = new ArrayList<>();
            while (rs.next())
            {
                User u = new User();
                u.setId(rs.getInt("id"));
                u.setUsername(rs.getString("username"));
                u.setPassword(rs.getString("password"));
                us.add(u);
            }
            return us;
        }
    }, 5);
    

    如果使用Java8新增的lambda表达式,还可以进一步简化:

    List<User> users = JdbcUtils.query("SELECT * FROM users WHERE id > ?", rs ->
    {
        List<User> us = new ArrayList<>();
        while (rs.next())
        {
            User u = new User();
            u.setId(rs.getInt("id"));
            u.setUsername(rs.getString("username"));
            u.setPassword(rs.getString("password"));
            us.add(u);
        }
        return us;
    }, 5);
    

    query函数的异常处理

    query函数不仅封装了查询操作的流程,同时也封装了异常处理。在query函数的实现中,如果JDBC API抛出了异常,则将异常封装成RuntimeException,然后直接向外抛出:

    catch (Exception e)
    {
        throw new RuntimeException(e);
    }
    

    为什么要这么做呢?

    首先,为了增强工具类的可用性,我们既要允许用户就近处理工具类抛出的异常,也不希望强制用户每次在调用工具类的方法时都加上try{...}catch{...}结构。结合以上两点,就采取了上述“封装成RuntimeException再重新抛出”的方法。采用这种方法,既可以保证异常传递出去,也不会强制用户捕获异常。

    如果用户想就近处理异常,只需写如下代码:

    try
    {
        // 调用JdbcUtils.query
    }
    catch (Exception e)
    {
        // 异常处理
    }
    

    如果用户使用了更高级的异常处理技术(如Spring框架的AOP),不想在数据访问层处理异常,而是在更高的层次对异常进行统一处理,则只需把try{...}catch{...}结构去掉,让JdbcUtils抛出的异常继续向上层抛出。

    总结

    到目前为止,我们把JDBC的查询操作封装成了一个query函数,用户在调用函数的时候只需传入sql语句、sql参数和结果集转换器,就能直接获取查询结果,再也不用写繁琐冗长的JDBC原生API了!

  • 相关阅读:
    python 装饰器
    python 环境迁移之requirements.txt (window环境)
    selenium 元素查找与属性
    pytest+allure(allure-pytest基于这个插件)设计定制化报告
    Capability配置
    python_excel
    python_连接mysql
    MySql_1
    appium_环境搭建
    appium_appium简介
  • 原文地址:https://www.cnblogs.com/baiyuxuan/p/14329597.html
Copyright © 2020-2023  润新知