zoukankan      html  css  js  c++  java
  • 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更新操作的封装。

  • 相关阅读:
    Leetcode 238. Product of Array Except Self
    Leetcode 103. Binary Tree Zigzag Level Order Traversal
    Leetcode 290. Word Pattern
    Leetcode 205. Isomorphic Strings
    Leetcode 107. Binary Tree Level Order Traversal II
    Leetcode 102. Binary Tree Level Order Traversal
    三目运算符
    简单判断案例— 分支结构的应用
    用switch判断月份的练习
    java基本打印练习《我行我素购物系统》
  • 原文地址:https://www.cnblogs.com/baiyuxuan/p/14329620.html
Copyright © 2011-2022 走看看