Spring-JDBC 源码学习(5) —— ResultSet

ResultSet

在前几节已经提到讲了数据源、驱动管理器以及 Statement 之后,利用 JDBC 的最重要的目的就是对 DB 进行操作,并获得预期结果。对于查询语句而言,结果应该是若干记录行;就更新语句而言,结果可能是影响的行数。而 Spring-jdbc 对 ResultSet 额外进行的封装,即是将原本散乱的结果进行一个整合,例如整合成一个(一组)完整的 Bean 来进行展示。

在 JdbcTemplate 中,四个基本方法入参都包括一个回调接口,而在执行回调获得 ResultSet 之后,方法并不是直接返回,而是进行了一定的操作。

以一个 PreparedStatementCallback 的实例类为例,在 doInPreparedStatement() 方法中,获得了 ResultSet ,但是仍通过 rse.extractData(rs) 语句进行了处理后再返回结果

public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
    // 声明一个 ResultSet 
    ResultSet rs = null;
    try {
        if (pss != null) {  // setValues() 方法填充 PreparedStatement 中的可变参数 ? 
            pss.setValues(ps);
        }
        rs = ps.executeQuery(); // 执行查询 sql ,获取结果
        return rse.extractData(rs); // 重点... 该语句一定是对结果进行了一些操作.
    }
    finally {
        JdbcUtils.closeResultSet(rs);
        if (pss instanceof ParameterDisposer) {
            ((ParameterDisposer) pss).cleanupParameters();
        }
    }
}

在来看一下究竟在返回结果前进行了什么操作。

由于是一个回调接口的实现类,rse 应该在外部方法中。

public <T> T query(
        PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, 
        final ResultSetExtractor<T> rse)
        throws DataAccessException {

    return execute(psc, new PreparedStatementCallback<T>() {
        @Override
        public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
            ... 
        }
    });
}

可以看到 rse 是一个 ResultSetExtractor 的实例。从名称上可以看出 Extractor 即为提取器,结果集提取器。

/** 函数式接口,提供的唯一方法为 extractData(...) */
@FunctionalInterface
public interface ResultSetExtractor<T> {

    @Nullable
    T extractData(ResultSet rs) throws SQLException, DataAccessException;

}

Spring-jdbc 中现有的实现有三个类,其中 RowCallbackHandlerResultSetExtractor 和 RowMapperResultSetExtractor 两个类从上面类图及命名即可看出,两个是作为一个适配器而存在的,将 extractData() 进行转换,分别由 RowCallbackHandler 和 RowMapper 实例进行具体操作。而 AbstractLobStreamingResultSetExtractor 类从名称上看,也是一个类似流操作的实现类(其中的 Lob 指的是 DB 中的 BLOB 与 CLOB 类型,在 Spring-jdbc 中也加入了额外的支持) 。

RowCallbackHandler

从名称上看,这是一个与 ResultSet 结果集行相关的回调接口。processRow(ResultSet rs) 方法将处理行相关的逻辑。

从它的一个实现类 RowCountCallbackHandler 来说,其最主要的私有属性 rowCount 即是统计 ResultSet 的结果行数。下面是一个具体使用的该类的案例:

public static void main(String[] args) throws ClassNotFoundException {

    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setUrl("jdbc:mysql://<host>:<port>/<dbName>");
    dataSource.setUsername("<username>");
    dataSource.setPassword("<password>");

    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    RowCountCallbackHandler rcch = new RowCountCallbackHandler();

    jdbcTemplate.query("SELECT * FROM info WHERE id='2018'", 
                        (RowCallbackHandler) rcch);


    System.out.println(rcch.getRowCount()); //获取结果集行数
    System.out.println(rcch.getColumnCount());  // 获取结果集列数
    for (String arg : rcch.getColumnNames()) {  // 打印结果集每一列名称
        System.out.println("ColumnNames : " + arg);
    }
    for (int i : rcch.getColumnTypes()) {   // 打印结果集每一列类型(Types 为枚举类)
        System.out.println("ColumnTypes : " + i);
    }
}

具体查看 RowCountCallbackHandler 类的 processRow() 方法可以看到,其获取列相关信息都来自于 ResultSetMetaData 。而结果行数来源于 Iterator 迭代。

@Override
public final void processRow(ResultSet rs) throws SQLException {
    if (this.rowCount == 0) {
        ResultSetMetaData rsmd = rs.getMetaData();
        this.columnCount = rsmd.getColumnCount();
        this.columnTypes = new int[this.columnCount];
        this.columnNames = new String[this.columnCount];
        for (int i = 0; i < this.columnCount; i++) {
            this.columnTypes[i] = rsmd.getColumnType(i + 1);
            this.columnNames[i] = JdbcUtils.lookupColumnName(rsmd, i + 1);
        }
        // could also get column names
    }
    processRow(rs, this.rowCount++);
}

RowMapper

上面 RowCallbackHandler 接口从逻辑上划分是用于处理 ResultSet 元数据信息的。而 RowMapper 较上一个接口而言,有更高的实用性。

特别是其实现类 BeanPropertyRowMapper 可以将散乱的 ResultSet 的结果数据整理成一个个 Object Bean 作为返回结果。而省去了对逐一读取 ResultSet 行记录并填充 Bean 属性的麻烦。

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

BeanPropertyRowMapper<Model> rowMapper = new BeanPropertyRowMapper<>(Model.class);
List<Model> list = jdbcTemplate.query(
                            "SELECT * FROM info WHERE id = '2018'", rowMapper);
/** 获得的 Model list 的对应属性将通过 Java 的反射机制进行填充
 *  List<Model> list 即结果
 */

当然,要求是 Model 类中的属性与 DB table 中的列名保持一致(大小写无要求)。

而其它两个类的实现也是基于反射机制,实现其相应的业务逻辑。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring-jdbcSpring框架提供的一种数据库访问方式,它基于JDBC API实现,但在使用上比JDBC更加简洁和易用。下面是Spring-jdbc的使用方法: 1. 配置数据源:在Spring的配置文件中配置一个数据源,可以使用Spring-jdbc内置的数据源或者自己实现数据源。 2. 编写DAO类:使用Spring-jdbc访问数据库需要编写DAO类。DAO类需要继承Spring提供的JdbcDaoSupport类,这样就能够获得一个JdbcTemplate对象,用于执行SQL语句。 3. 编写SQL语句:在DAO类中编写SQL语句,可以使用JdbcTemplate对象提供的方法执行SQL语句。 4. 对SQL语句的结果进行处理:对于查询语句,JdbcTemplate提供了可以返回结果集的方法,可用于遍历结果集、映射结果集到Java对象、统计结果集等操作。 下面是一个简单的使用Spring-jdbc访问MySQL数据库的示例: 首先在Spring的配置文件中配置MySQL数据源: ``` <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> ``` 然后编写DAO类,此示例中使用JdbcTemplate对象执行SQL语句并返回结果: ``` public class UserDao extends JdbcDaoSupport { public User getUserById(int id) { String sql = "SELECT id,name,age FROM user WHERE id=?"; return getJdbcTemplate().queryForObject(sql, new Object[]{id}, new RowMapper<User>() { @Override public User mapRow(ResultSet rs, int rowNum) throws SQLException { User user = new User(); user.setId(rs.getInt("id")); user.setName(rs.getString("name")); user.setAge(rs.getInt("age")); return user; } }); } } ``` 最后在需要使用数据库的地方调用DAO类中的方法即可完成数据库操作。 ``` UserDao userDao = new UserDao(); User user = userDao.getUserById(1); System.out.println(user.getName()); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值