JDBC工具类——JdbcUtils(5)

JDBC工具类——JdbcUtils(5)

前言

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

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

回顾

到目前为止,JDBC的查询操作已经封装得差不多了,结果集转换器和行转换器这两个抽象让查询操作使用起来非常方便,用户不仅可以直接使用预定义的转换器,还可以自定义转换器,基本能实现各种常见的查询需求。但是还是存在一些不完美的地方。

ResultSet暴露引发的问题

ResultSetMapper<T>接口和RowMapper<T>接口的map方法的定义中,向用户直接暴露了ResultSet参数:

T map(ResultSet rs) throws Exception;

这导致用户在实现自定义转换器的过程中,可能无意或有意地调用了ResultSet中的一些关键方法,从而导致程序崩溃。

举个例子,假如某个用户在实现ResultSetMapper<T>的过程中,“自作聪明”地在map方法的最后调用了ResultSetclose方法:

public class UserListResultSetMapper implements ResultSetMapper<List<User>>
{
    @Override
    public List<User> map(ResultSet rs) throws Exception
    {
        ...
        rs.close();
        return XXX;
    }
}

这将会引发程序崩溃,因为在query函数中也会对结果集进行关闭,导致ResultSet被关闭了两次。

另一个问题,就是用户在实现RowMapper<T> 时,可能在map方法中调用ResultSetnext函数,这会导致结果集向前移动一行:

public class UserRowMapper implements ResultSetMapper<User>
{
    @Override
    public User map(ResultSet rs) throws Exception
    {
        ...
        rs.next();
        ...
        return XXX;
    }
}

当然,我们可以在文档中加上说明,提醒用户实现这两个接口时的注意事项,但是并不是每个用户使用前都会仔细阅读文档。

封装ResultSet

为了解决这个问题,我们不能把ResultSet暴露给用户,所以需要把ResultSet封装起来。我们把封装后的结果集分离成RecordRow两个接口:

public interface Record
{
    Row getCurrentRow(); // 获取当前行
    boolean next(); // 移动到下一行
}

public interface Row
{
    Object getObject(String columnLabel);
    Object getObject(int columnIndex);
    int getInt(String columnLabel);
    int getInt(int columnIndex);
    String getString(String columnLabel);
    String getString(int columnIndex);
    double getDouble(String columnLabel);
    double getDouble(int columnIndex);

    int getColumnCount(); // 获取列数
    String getColumnLabel(int index); // 获取列标签
}

Record表示整个结果集,它是对数据库查询操作返回结果的封装。一个结果集由若干个数据行组成。初始时,结果集的当前行指向第一行之前,调用next方法可以让当前行向前移动一行。若当前已到达最后一行,则next调用返回false,否则返回true

Row表示数据行,封装了结果集的一行数据。数据行由若干列组成,每列都是一个值。Row中包含了一系列getXXX方法用于获取列中的值。

Record中的getCurrentRow方法用于获取当前行。

重构ResultSetMapper<T>RowMapper<T>

有了RecordRow,就可以对之前的两个转换器接口做重构:

public interface RecordMapper<T>
{
    T map(Record record);
}

public interface RowMapper<T>
{
    T map(Row row);
}

注意,这里把ResultSetMapper<T>重命名成了RecordMapper<T>

此时RecordRow接口中已经没有像close一样危险的方法,所以再也不用担心用户在map方法中搞破坏了。

实现RecordRow接口

接下来,需要使用适配器模式ResultSet转换成RecordRow

public class RecordAdapterForResultSet implements Record, Row
{
    private final ResultSet rs;

    public RecordAdapterForResultSet(ResultSet resultSet)
    {
        this.rs = resultSet;
    }

    @Override
    public Object getObject(String columnLabel)
    {
        try
        {
            return rs.getObject(columnLabel);
        }
        catch (SQLException e)
        {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    @Override
    public Object getObject(int columnIndex)
    {
        try
        {
            return rs.getObject(columnIndex);
        }
        catch (SQLException e)
        {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    // 其它getXXX方法...

    @Override
    public int getColumnCount()
    {
        try
        {
            ResultSetMetaData metaData = rs.getMetaData();
            return metaData.getColumnCount();
        }
        catch (SQLException e)
        {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    @Override
    public String getColumnLabel(int index)
    {
        try
        {
            ResultSetMetaData metaData = rs.getMetaData();
            return metaData.getColumnLabel(index);
        }
        catch (SQLException e)
        {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    @Override
    public Row getCurrentRow()
    {
        return this;
    }

    @Override
    public boolean next()
    {
        try
        {
            return rs.next();
        }
        catch (SQLException e)
        {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
}

RecordAdapterForResultSet类将ResultSet包装成了RecordRow,同时封装了异常处理。

修改query方法

query方法做出的修改如下:

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

其中,try块中最后一行由

return resultSetMapper.map(rs);

改成了

return recordMapper.map(new RecordAdapterForResultSet(rs));

总结

到这里,对JDBC查询操作的封装就结束了。下一篇文章将介绍JDBC更新操作的封装。

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