- 回顾
- ResultSetHandler
- DefaultResultSetHandler
- handlerResultSets方法
- ResultSet的迭代过程
- ResultSetWrapper
- 构造方法
- 映射
前面我们已经了解了MyBatis的整个初始化过程,与SQL节点的解析与SQL节点的SQL是如何与实参进行绑定起来、如何根据实参进行动态拼接,下面来看一下MyBatis是如何处理结果集的
ResultSetHandlerMyBatis会根据SQL映射配置文件中定义的映射规则,比如resultMap标签、resultType属性来进行结果集映射,映射成相应的结果对象,而结果集映射机制是MyBatis的核心机制之一
当StatementHandler接口在执行完指定的select语句时候,就会调用ResultSetHandler来处理结果集,ResultSetHandler就是负责完成映射处理的,这里我们先不用知道StatementHandler如何执行SQL,我们只认识了DynamicSqlSource和RawSqlSource来存储SQL节点中完成解析的SQL而已
该接口提供三种方法,处理不同的结果集
- handlerResultSets:处理结果集,生成相应的结果对象集合
- handlerCursorResults:处理返回的游标对象
- handlerOutputParameters:处理存储过程的输出参数的
MyBatis仅仅只有一个DefaultResultSetHandler实现了ResultSetHandler接口
下面来介绍一下其比较重要的成员属性
- executor:处理器,用来执行SQL的
- configuration:MyBatis的上下文配置
- RawBounds:用于分页的
- mappedStatement:存储SQL节点的
- resultHandler:指定用于处理结果集的ResultHandler对象,ResultSetHandler其实是一个组合对象而已,真实处理是交由ResultHandler来进行
- TypeHandlerRegistry:类型转换注册中心
- ObjectFactory:用于实例对象工厂的
- ReflectorFactory:反射工厂,用于获取类的Reflector,根据Reflector可以创建对象出来
处理结果集映射的是handlerResultSets方法,该方法不仅可以处理普通的SQL语句(Statement)、也能处理预编译SQL(PrepareStatement),还有存储过程(CallableStatement)
源码如下
public List
整个的处理过程如下
-
获取第一个结果集,也就是第一个ResultSet
-
获取前面初始化SQL节点时得到的ResultMap集合
-
判断第一个结果集是否为空,如果为空,允许ResultMap集合也为空,如果存在结果集,那么对应的ResultMap集合也不能为空,这也是为什么要去获取第一个ResultSet的缘故
-
然后去迭代ResultSet和ResultMap,使用对应的ResultMap来处理结果集,一个结果集对应一个ResultMap对象,也就是说ResultMap的对象必须要大于等于结果集数量
-
其实仅仅只是像迭代器一样,遍历ResultSet,然后使用对应的ResultMap来处理而已,而迭代器其实就是Statement本身,通过getNextResults来进行获取下一个结果集
-
对于每一个结果集如果不为空,则会封装成ResultSetWrapper
-
最后进行处理嵌套映射(一般不会使用嵌套映射)
如何获取第一个ResultSet结果集?
获取第一个ResultSet结果集,其实可以说是获取它的一个ResultSet迭代器,也就是ResultSetWrapper对象
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException { //调用Statement去获取结果集 ResultSet rs = stmt.getResultSet(); //对空进行循环,这是针对HSQLDB2.1版本的页特殊处理 //因为HSQLDB2.1第一次并不会返回结果集 while (rs == null) { // move forward to get the first resultset in case the driver // doesn't return the resultset as the first result (HSQLDB 2.1) //判断有没有后续结果集 if (stmt.getMoreResults()) { //继续迭代,如果有,就会继续循环判断是否为空 //如果有且不为空,那就找到了 rs = stmt.getResultSet(); } //如果没有后续结果集 else { //break if (stmt.getUpdateCount() == -1) { // no more results. Must be no resultset break; } } } //如果存在结果集,就封装ResultSetWrapepr返回,否则返回Null return rs != null ? new ResultSetWrapper(rs, configuration) : null; }
可以看到,获取第一个结果集的方式其实就是判断遍历整个结果集,获取第一个非空的结果集,如果不存在,则会返回Null,如果存在,封装成ResultWrapper对象返回
获取第一个非空的结果集,这是为了支持HSQLDB2.1第一次通过Statement获取ResultSet可能为空
如何获取下一个结果集?
对应的方法为getNextResultSet(stmt)
private ResultSetWrapper getNextResultSet(Statement stmt) { // Making this method tolerant of bad JDBC drivers try { if (stmt.getConnection().getmetaData().supportsMultipleResultSets()) { // Crazy Standard JDBC way of determining if there are more results if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) { //通过Statement继续获取ResultSet ResultSet rs = stmt.getResultSet(); if (rs == null) { return getNextResultSet(stmt); } else { return new ResultSetWrapper(rs, configuration); } } } } catch (Exception e) { // Intentionally ignored. } return null; }
可以看到,其实就是继续通过Statement继续去获取下一个非空的ResultSet,然后封装成ResultSetWrapper对象返回
其实ResultSet的迭代过程几乎是交由Statement去完成的
ResultSetWrapper前面已经提到过,对于ResultSet结果集,MyBatis并不是直接使用,而是封装成一个ResultSetWrapper,下面就看看这个ResultSetWrapper是处理什么的
成员属性
- resultSet:底层的结果集对象
- TypeHandlerRegistry:类型转换注册中心
- columnNames:结果集中的每一列的列名
- classNames:每一列对应的java类型
- jdbcTypes:每一列的jdbc类型
- typeHandlerMap:每列对应的TypeHandler
- mappedColumnNamesMap:记录被映射的列名,value为被映射的列名,而key为resultMap的ID
- unMappedColumnNamesMap:记录未被映射的列名,value为未被映射的列名,而key为resultMap的ID
说白了,ResultSetMapper其实封装ResultSet结果集中进行列映射的信息,比如使用到的TypeHandler、列的jdbcType和对应的javaType,还有不进行映射的列(SQL查出来了,但ResultMap没有进行对应的配置来映射该列)、进行映射的列,可以通过ResultSetWrapper来获取到结果集映射的一系列信息,比如要映射的列名、不需要映射的列名、列对应的java类型、列对应的jdbc类型、列需要使用到的TypeHandler
构造方法上面的一系列集合,都是ResultSetWrapper通过哦构造方法来进行初始化的
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException { super(); //从Configuration中取出TypeHandlerRegistry this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); this.resultSet = rs; //通过结果集获取metaData(JDBC的ResultSetmetaData,可以根据其获取结果集的信息) final ResultSetmetaData metaData = rs.getmetaData(); final int columnCount = metaData.getColumnCount(); //遍历列 for (int i = 1; i <= columnCount; i++) { //获取所有的列名、这里可能是别名 //封装在columnNames中 columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i)); //并且获取列对应的JdbcType,然后封装在jdbcTypes集合中 jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i))); // classNames.add(metaData.getColumnClassName(i)); } }
下面来看是如何获取TypeHandler的,前面我们已经看到了,TypeHandler是用来做类型转换的
public TypeHandler> getTypeHandler(Class> propertyType, String columnName) { TypeHandler> handler = null; //从typeHandlerMap中获取TypeHandler Map映射, TypeHandler>> columnHandlers = typeHandlerMap.get(columnName); if (columnHandlers == null) { columnHandlers = new HashMap<>(); typeHandlerMap.put(columnName, columnHandlers); } else { handler = columnHandlers.get(propertyType); } if (handler == null) { JdbcType jdbcType = getJdbcType(columnName); handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType); // Replicate logic of UnknownTypeHandler#resolveTypeHandler // See issue #59 comment 10 if (handler == null || handler instanceof UnknownTypeHandler) { final int index = columnNames.indexOf(columnName); final Class> javaType = resolveClass(classNames.get(index)); if (javaType != null && jdbcType != null) { handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType); } else if (javaType != null) { handler = typeHandlerRegistry.getTypeHandler(javaType); } else if (jdbcType != null) { handler = typeHandlerRegistry.getTypeHandler(jdbcType); } } if (handler == null || handler instanceof UnknownTypeHandler) { handler = new ObjectTypeHandler(); } columnHandlers.put(propertyType, handler); } return handler; }
前面已经提到了,对于每一个ResultSet会封装成一个ResultSetWrapper,里面可以获取映射的一系列信息,然后DefaultResultSetHandler的handlerResultSet可以进行简单映射和复杂的嵌套映射
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)