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了!

posted @ 2021-01-26 12:01  baiyuxuan  阅读(160)  评论(0编辑  收藏  举报