Mybatis框架源码笔记(七)之Mybatis中类型转换模块(TypeHandler)解析

news2024/11/27 3:55:19

1、JDBC的基本操作回顾

这里使用伪代码概括一下流程:

    1. 对应数据库版本的驱动包自行下载加载驱动类
(Class.forName("com.mysql.cj.jdbc.Driver"))
    1. 创建Connection连接:
conn = DriverManager.getConnection("jdbc:mysql://数据库IP:port/数据库名称?useUnicode=true&characterEncoding=utf8", "用户名", "用户密码");
    1. 准备SQL语句:
String sql = "select * from lib_book where book_id = ?";
    1. 创建预处理语句对象
PreparedStatement ps = conn.prepareStatement(sql)
    1. 输入参数处理
// 需要根据参数索引位置和参数类型替换对应占位符索引位置上的?为实际的参数值 例如 ps.setInt(1, 12)
ps.setInt(1, 10);
ps.setString(2, "老人与海");
    1. 执行SQL语句

查询操作:

ResultSet resultSet = ps.executeQuery();

修改操作:

ps.execute();
    1. 处理返回结果集
while(resultSet.next()){ 
	// 取出一行数据来进行处理,映射成java实体类 	LibBook book = new LibBook(); 	 
    book.setBookId(resultSet.getLong(1));
    book.setBookIndexNo(resultSet.getString(2));
    book.setBookName(resultSet.getString(3));
    book.setBookAuthor(resultSet.getString(4));
    book.setBookPublisher(resultSet.getString(6));
    book.setBookCateId(resultSet.getInt(7));
    book.setBookStock(resultSet.getInt(8)); }
    1. 依次关闭打开的所有句柄对象(ResultSet、PreparedStatement、Connection)
 if(statement != null) {
     try {
         statement.close();
     } catch (SQLException e) {
         e.printStackTrace();
     }
 }

 if(conn != null) {
     try {
         conn.close();
     } catch (SQLException e) {
         e.printStackTrace();
     }
 }

2、Myabtis框架对于JDBC操作的简化

总结一下Mybatis框架的使用流程:

1) pom.xml文件中引入Mybatis、日志框架、单元测试框架及数据库驱动依赖

2) 编写mybatis-config.xml全局配置文件、日志配置文件

3) 编写Mapper层接口
 
4) 编写Mapper层接口对应的xml映射文件, OK搞定。

单元测试的使用过程:

1) 创建一个SqlSessionFactoryBuilder对象:SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();	
 
2) 创建一个SqlSessionFactory对象:SqlSessionFactory factory = factoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml")); 

3) 创建一个SqlSession对象:SqlSession sqlSession = factory.openSession();

4) 获取Mapper层指定接口的动态代理对象:LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
 
5) 调用接口方法获取返回结果即可: List<LibBook> list = mapper.selectAllBook();

对比操作, 我们发现使用Mybatis框架之后,我们不需要在关注JDBC的具体操作操作过程了,输入参数和返回结果的映射转换
不用我们再手动处理了, 资源的释放也不需要我们手动处理了,只需要调用简单的应用层接口就可以完成所有的操作,太疯狂了。

但是对于一个合格的开发人员, 我们对于框架的API熟练使用, 还要对其中的原理清楚了解, 这样才能再出现问题时,我们可以修改和扩展原有框架完成我们想要的功能, 扯远了, 今天我们主要来聊一聊Mybatis框架究竟是如何封装JDBC中处理输入参数和返回结果集的类型转换的功能的?

3、Myabtis框架的类型转换模块

类型转换模块属于Mybatis框架的基础支撑层模块,主要实现数据库中数据Java对象中的属性双向映射
主要就是在以下两种场景钟会使用到:

 1)在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换为JDBC类型;
 
 2)从ResultSet结果集中获取数据时,则需要从JDBC类型转换为Java类型;

3.1 如何完成javaType和JDBCType类型互转

在这里插入图片描述

3.2 Mybatis的设计实现

在这里插入图片描述

3.2.1 TypeHandler接口

MyBatis框架中提供的所有的类型转换器实现类都继承了TypeHandler接口,在TypeHandler接口中定义了类型转换器的最基本的功能(处理输入参数和输出参数)。

/*
 *    Copyright 2009-2023 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author Clinton Begin
 * 类型处理器
 */
public interface TypeHandler<T> {


  /**
   * 完成SQL语句中实际参数替换占位符的方法
   */
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Gets the result.
   * 通过数据库列名称从ResultSet中获取结果数据
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
   * @return the result
   * @throws SQLException
   *           the SQL exception
   */
  T getResult(ResultSet rs, String columnName) throws SQLException;

  /**
   * 通过数据库列索引从ResultSet中获取结果数据
   */
  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  /**
   * 通过数据库列索引从存储过程的执行书写出结果中获取结果数据
   */
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

在这里插入图片描述

3.2.2 BaseTypeHandler实现类

这个基础实现类中只提供了通用了类型转换的基本实现,并在原有功能上进行了扩展。
在这里插入图片描述
但是对于不同的数据类型的互转还是要根据类型进行针对性处理,比如数字类型、字符串类型、日期类型等等,处理方法肯定不同,mybatis框架提供了这些常用类型的转换器实现

类型处理器Java 类型JDBC 类型
BooleanTypeHandlerjava.lang.Boolean, boolean数据库兼容的 BOOLEAN
ByteTypeHandlerjava.lang.Byte, byte数据库兼容的 NUMERIC 或 BYTE
ShortTypeHandlerjava.lang.Short, short数据库兼容的 NUMERIC 或 SMALLINT
IntegerTypeHandlerjava.lang.Integer, int数据库兼容的 NUMERIC 或 INTEGER
LongTypeHandlerjava.lang.Long, long数据库兼容的 NUMERIC 或 BIGINT
FloatTypeHandlerjava.lang.Float, float数据库兼容的 NUMERIC 或 FLOAT
DoubleTypeHandlerjava.lang.Double, double数据库兼容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal数据库兼容的 NUMERIC 或 DECIMAL
StringTypeHandlerjava.lang.StringCHAR, VARCHAR
ClobReaderTypeHandlerjava.io.Reader-
ClobTypeHandlerjava.lang.StringCLOB, LONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHAR, NCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream-
ByteArrayTypeHandlerbyte[]数据库兼容的字节流类型
BlobTypeHandlerbyte[]BLOB, LONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAny OTHER或未指定类型
EnumTypeHandlerEnumeration TypeVARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)
EnumOrdinalTypeHandlerEnumeration Type任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandlerjava.lang.StringSQLXML
InstantTypeHandlerjava.time.InstantTIMESTAMP
LocalDateTimeTypeHandlerjava.time.LocalDateTimeTIMESTAMP
LocalDateTypeHandlerjava.time.LocalDateDATE
LocalTimeTypeHandlerjava.time.LocalTimeTIME
OffsetDateTimeTypeHandlerjava.time.OffsetDateTimeTIMESTAMP
OffsetTimeTypeHandlerjava.time.OffsetTimeTIME
ZonedDateTimeTypeHandlerjava.time.ZonedDateTimeTIMESTAMP
YearTypeHandlerjava.time.YearINTEGER
MonthTypeHandlerjava.time.MonthINTEGER
YearMonthTypeHandlerjava.time.YearMonthVARCHAR 或 LONGVARCHAR
JapaneseDateTypeHandlerjava.time.chrono.JapaneseDateDATE

这里通过一个具体的基础数据类型BooleanTypeHandler类来进行说明

/*
 *    Copyright 2009-2022 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author Clinton Begin
 */
public class BooleanTypeHandler extends BaseTypeHandler<Boolean> {

  /**
   * 预处理语句中的非null参数转换(null参数在哪里处理的呢, 在父类BaseTypeHandler里面处理了)
   */
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setBoolean(i, parameter);
  }

  /**
   * ResultSet结果集里面jdbcType返回为null时通过调用该方法"传递列名方式"转换成Java类中对应属性的javaType
   */
  @Override
  public Boolean getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    boolean result = rs.getBoolean(columnName);
    return !result && rs.wasNull() ? null : result;
  }

  /**
   * ResultSet结果集里面jdbcType返回为null时通过调用该方法"传递列索引方式"转换成Java类中对应属性的javaType
   */
  @Override
  public Boolean getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    boolean result = rs.getBoolean(columnIndex);
    return !result && rs.wasNull() ? null : result;
  }

  /**
   * 存储过程执行结果转换成javaType类型
   */
  @Override
  public Boolean getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    boolean result = cs.getBoolean(columnIndex);
    return !result && cs.wasNull() ? null : result;
  }
}

3.2.3 TypeHandlerRegistry注册器

Mybatis框架提供了很多的类型转换器实现给我们使用, 这些类型转换器对象肯定不是需要我们自己进行创建的, 肯定是在Mybatis集成到我们的项目中时就已经完成所有
默认的类型转换器的实例化的,那么Myabtis框架是怎么保存这些对象的么? 这些对象又是什么时候完成的实例化操作的么?
就是通过 TypeHandlerRegistry类型转换器完成的。
在这里插入图片描述在TypeHandlerRegistry注册器的构造器中完成了java中常用数据类型与jdbc对应数据类型转换时需要用到的TypeHandler类对象的创建和注册
在这里插入图片描述

3.2.4 TypeAliasRegistry别名注册器

MyBatis框架的应用的时候会经常用到别名,这能大大简化我们的代码,其实在MyBatis中是通过TypeAliasRegistry类管理的。在TypeAliasRegistry类的构造方法中会注入系统常见类型的别名。
在这里插入图片描述
别名注册器中的核心方法如下:
在这里插入图片描述

  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748  将别名先转成小写字母
    String key = alias.toLowerCase(Locale.ENGLISH);
    // 判断别名是否存在
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    // 将别名添加到aliasMap集合中去
    typeAliases.put(key, value);
  }

Mybatis自定义别名注册的两种方式:

  • 第一 配置文件方式 (mybatis-config.xml)
<typeAliases>
	<package name="com.baidu"/>
</typeAliases> 
  • 第二 注解方式 @Alias
@Alias("People")
public class SysPeople {
	private String name;
}

自定义别名注册也是就是通过这个registerAliases(String packageName, Class<?> superType) 方法来进行解析注册的。

  /**
   * 通过package指定别名路径和通过@Alisa注解来注册别名的方法
   * 例如我们在全局配置文件中配置的 <typeAliases> <package name="com.baidu"/></typeAliases> 就是通过这个方法来解析处理的
   *  还有我们使用@Alias()注解为类注册的别名都是通过这个方法来完成注册的
   */
  public void registerAliases(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for (Class<?> type : typeSet) {
      // Ignore inner classes and interfaces (including package-info.java)
      // Skip also inner classes. See issue #6
      if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
        registerAlias(type);
      }
    }
  }

  public void registerAlias(Class<?> type) {
    String alias = type.getSimpleName();
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    }
    registerAlias(alias, type);
  }

3.2.5 如何自定义类型装换器

你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:
实现 org.apache.ibatis.type.TypeHandler 接口,
或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。
具体的示例官方已经提供了示例代码, 有兴趣可以自行观看文档, 这里贴上传送门:自定义类型转换器文档及示例代码地址:https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers

3.3 Mybatis中Typehandler的应用

不管Mybatis框架还是SQL语句的执行流程经过这么长时间的接触, 大家肯定越来越熟悉的, 关于SQL语句输入参数的转换及SQL语句执行结果的处理流程节点大家应该能条件反射的说出来, 肯定是在SQL语句执行的时候进行处理的, Myabtis框架中执行SQL语句都是依赖Executor来完成的.

关于SQL的参数处理及ResultSet的处理肯定也在这个接口的某个实现类中完成的, 这里就不卖关子了, 直接亮剑

3.3.1 输入参数处理

主要涉及的核心类就是SimpleExecutorPreparedStatementHandlerDefaultParameterHandler,来看看每个类中关于输入参数处理的核心方法。

  • SimpleExecutor

SQL语句预处理的核心方法如下

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 创建数据库连接会话,从这里可以看出数据库连接的创建时延迟创建的, 在真正使用的时候才会创建,这里我们可以做扩展,
    // 数据库的读写分离可以通过这个特性通过扩展实现
    Connection connection = getConnection(statementLog);
    // 创建预处理SQL语句对象
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 预处理SQL语句对象中的参数占位符替换处理
    handler.parameterize(stmt);
    return stmt;
  }
三件事
1、创建数据库连接会话
2、创建预处理SQL语句对象,
3、如果SQL语句中有查询条件,使用了参数占位符, 就将实际的参数替换成真实的输入参数

看看上面这段代码不就是对应JDBC里面这段代码么,对不对
在这里插入图片描述
只是Mybatis框架做了面向对象层次的封装, 其实核心就是上面那三件事
SimpleExecutor

  • PreparedStatementHandler

这里省略了关于handler.parameterize()方法的调用过程的解析, 直接说明一下吧, 就是运用了模板方法模式完成了StatementHandler的参数替换的功能,因为MappedStatement的StatementType默认的类型是PREPARED, 所以这里调用的是
PreparedStatementHandlerparameterize()方法,接下来看看真正处理输入参数的方法

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

上面的方法调用了ParameterHandler接口的setParameters()方法
在这里插入图片描述

3.3.2 ParameterHandler对象是什么时候创建的?

在SimpleExecutor对象的doQuery()或者doUpdate()方法中调用了创建StatementHanlderParameterHandlerResultSetHandler对象的入口,方法如下

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // StatementHanlder、ParameterHandler、ResultSetHandler创建的入口
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 预处理SQL语句对象创建
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 执行SQL语句并处理结果后返回
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

来看看configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);这个方法

 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 // 创建的statementhandler对象是一个RoutingStatementHandler()对象
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

看看RoutingStatementHandler的构造方法, 如下:
在这里插入图片描述
再看看RoutingStatementHandler的构造方法, 如下:
在这里插入图片描述
再看看BaseStatementHandler的构造方法,如下:
在这里插入图片描述

结论

在创建实际Statement对象对应的StatementHandler时, 完成了ParameterHandler和ResultHandler对象的创建。

3.3.3 ParameterHandler/ResultSethandler对象是谁?

接着上面往下聊, 看看3.3.2 BaseStatementHandler的构造方法中下面两行代码的执行过程

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

Configuration类中对应的方法如下:

  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
   	 // 这里我们使用的是**mapper.xml文件声明的SQL语句,所以这里的的LanguageDriver肯定是XMLLanguageDriver
   	 // XMLLanguageDriver.createParameterHandler()方法会创建一个DefaultParameterHandler对象
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 这里我们可以集成参数处理的插件来扩展入参处理的功能
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    // Mybatis框架默认创建的就是DefaultResultSetHandler对象  
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        // 这里我们可以集成结果集处理的插件来扩展结果集处理的功能
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

在这里插入图片描述
结论

如果没有自定义插件, 那么Mybatis框架会为我们创建框架提供的默认的DefaultParameterHandler和DefaultResultHandler对象来实现
SQL语句入参和查询结果集ResultSet转换的功能

3.3.4 DefaultParameterHandler中入参替换的方法实现

  • DefaultParameterHandler
  /**
   * 替换SQL语句中的占位符为实际传入的参数值的方法
   */
  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 从BoundSql对象中获取到参数映射对象集合
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        // 依次取出parameterMapping对象
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // 保证当前处理的parameterMapping对象都是输入参数
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          // 获取当前处理的parameterMapping对象的属性名称
          String propertyName = parameterMapping.getProperty();
          // 如果BoundSql对象的附加参数对象中包含该属性名称, 直接从BoundSql对象的附加参数对象中获取到该属性KEY对应的值
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
            // 如果ParameterObject为空,说明没有传值,值直接就为null
          } else if (parameterObject == null) {
            value = null;
            // 如果类型注册器中有该参数对象对应的类型处理器,则该参数取值就是parameterObject
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
            // 以上都不满足,就创建一个元数据对象,然后从元数据对象汇总通过属性获取到对应的取值
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // 获取当前parameterMapping对象的类型处理器
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          // 获取当前parameterMapping对象的JDBC数据类型
          JdbcType jdbcType = parameterMapping.getJdbcType();
          // 如果参数输入值为null并且数据库数据类型为null,就将jdbcType类型设置为OTHER类型
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // 为什么这里是使用i + 1?
            // insert into t_user(name, age, gender, email) value(?, ?, ?, ?)
            // 因为解析出来的带占位的sql语法中的?参数的计数是从1开始的, 不是从0开始的
            // 调用typeHandler的替换参数的方法替换到SQL语句中目标位置上占位符上为输入的参数值
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

3.3.5 输出结果处理

主要涉及的核心类就是SimpleExecutorPreparedStatementHandlerDefaultResultSetHandler,来看看每个类中关于查询结果集ResultSet处理的核心方法。

  • SimpleExecutor
  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 调用Statementhandler的query()方法执行查询,获取查询结果
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  • PreparedStatementHandler

调用resultSetHandler.handleResultSets(ps)方法处理查询结果集ResultSet

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    // 调用DefaultResultHandler的handleResultSets方法处理SQL语句的执行结果
    return resultSetHandler.handleResultSets(ps);
  }

3.3.6 DefaultResultSetHandler中ResultSet结果集转换的方法实现

handleResultSets()方法

  //
  // HANDLE RESULT SETS
  //
  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 处理每一行数据从ResultSet转换成java实体类的方法
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

处理每一行数据从ResultSet转换成java实体类的方法

 //
  // GET VALUE FROM ROW FOR NESTED RESULT MAP
  //

  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
    final String resultMapId = resultMap.getId();
    Object rowValue = partialObject;
    if (rowValue != null) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      putAncestor(rowValue, resultMapId);
      applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
      ancestorObjects.remove(resultMapId);
    } else {
      final ResultLoaderMap lazyLoader = new ResultLoaderMap();
      rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
      if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        if (shouldApplyAutomaticMappings(resultMap, true)) {
          // 自动完成jdbcType到javaType的映射
          foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        // 如果数据库字段和实体类属性无法自动映射, 需要通过该方法完成转换
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        putAncestor(rowValue, resultMapId);
        // 如果存在关联查询时, 需要完成来自其他数据表的ResultSet的转换处理
        foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
        ancestorObjects.remove(resultMapId);
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
      }
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
        nestedResultObjects.put(combinedKey, rowValue);
      }
    }
    return rowValue;
  }

可以自动映射时根据对应的TypeHandler返回对应类型的值。

  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }

根据Property调用对应的TypeHandler返回对应类型的值。

  private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    if (propertyMapping.getNestedQueryId() != null) {
      return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if (propertyMapping.getResultSet() != null) {
      addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
      return DEFERRED;
    } else {
      final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      return typeHandler.getResult(rs, column);
    }
  }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/402788.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最新消息:2023年软考高项教材改版!

最新通知&#xff1a;从2023年上半年软考开始信息系统项目管理师考试将依据新版考试大纲进行。 给备考高项的朋友的一些建议&#xff1a; 备考资源&#xff1a; 【腾讯文档】软考各科资料分享 https://docs.qq.com/doc/DTVN1SWtFZHdicUNp 复习方法&#xff1a; 选择题 选择题…

ChatGPT,乌合之众的疯狂

最近ChatGPT有多火爆就不用我说了。公司里&#xff0c;从CEO到技术人员&#xff0c;乃至于门口的保安、食堂的大婶&#xff0c;没有一个不会聊两句ChatGPT的。连我20年未见的小学同学、三线城市警官&#xff0c;都问我这东西能不能给领导写汇报材料。 用不了多久&#xff0c;家…

颠覆推特VS改造推特:什么是去中心化社交的正确姿势?

去年&#xff0c;“钢铁侠”伊隆马斯克收购了全球最大的社交媒体之一——推特。推特成立于2006年&#xff0c;是一个“公民广场”&#xff0c;允许大家公开发表观点和内容。用户可以关注自己喜欢的账号&#xff0c;也可以点赞转发评论他人的推文&#xff0c;中国的微博便是照搬…

【halcon】轮廓拟合相关算子

涉及函数 edges_sub_pix 寻找边缘 edges_sub_pix (Image, Edges, canny, 1, 10, 20) 后面三个参数&#xff0c;越小&#xff0c;找到的细节越多。这个是对应录波器为canny时。 canny滤波器用的最多。 segment_contours_xld 将连续的轮廓进行分段&#xff0c;按圆弧或者执…

JUC(七)

1.线程安全集合类 1>.线程安全集合类可以分为三大类: ①.遗留的(/旧的)线程安全集合,如:Hashtable,Vector; ②.使用Collections装饰的线程安全集合,如: Collections.synchronizedCollectionCollections.synchronizedListCollections.synchronizedMapCollections.synchroniz…

window通过wsl启动appsmith源码

window通过wsl启动appsmith前端后端前言appsmith前端本地启动WSL安装下载ubuntu升级wsl到wsl2ubuntu安装环境环境要求Ubuntu环境配置node下载解压运行[源码](https://www.appsmith.com/)本地访问后端appsmith后台本地启动启动mongo、rediswsl ubuntu中启动后台试试流程总结最后…

缓存双写一致性之更新策略探讨

问题由来 数据redis和MySQL都要有一份&#xff0c;如何保证两边的一致性。 如果redis中有数据&#xff1a;需要和数据库中的值相同如果redis中没有数据&#xff1a;数据库中的值是最新值&#xff0c;且准备会写redis 缓存操作分类 自读缓存读写缓存&#xff1a; &#xff0…

关于vuex的使用

1.首先安装vuex npm install vuex --save 这时如果直接安装vuex&#xff0c;不指定版本的话&#xff0c;就会直接安装最新的vuex的版本。所以会出现报错。 报错就安装这个 npm install --save vuex3 2.创建文件夹&#xff0c; 有的时候安装好会自动创建vuex的文件夹 &#xf…

Python解题 - CSDN周赛第35期 - 不算题解的题解

本期四道题还是全考过&#xff0c;题解在网上也都搜得到。。。只好继续水一份不算题解的题解。 第一题&#xff1a;交换后的or 给定两组长度为n的二进制串&#xff0c;请问有多少种方法在第一个串中交换两个不同位置上的数字&#xff0c;使得这两个二进制串“或”的结果发生改…

案例01-修改数据redis没有同步更新

目录 一&#xff1a;背景介绍 二&#xff1a;思路&方案 三&#xff1a;过程 1.修改数据没有删除缓存 2.修改数据删除了缓存 四&#xff1a;总结 五&#xff1a;升华 一&#xff1a;背景介绍 redis中存储了关于一个课程下多个班级的信息。但是难免会在一个课程下添加新…

pandas 数据预处理+数据概览 处理技巧整理(持续更新版)

这篇文章主要是整理下使用pandas的一些技巧&#xff0c;因为经常不用它&#xff0c;这些指令忘得真的很快。前段时间在数模美赛中已经栽过跟头了&#xff0c;不希望以后遇到相关问题的时候还去网上查&#xff08;主要是太杂了&#xff09;。可能读者跟我有一样的问题&#xff0…

程序员养发神器:拒绝加班熬夜,告别秃头!

身为一个程序员&#xff0c;每天的工作就是写代码和吹牛逼&#xff0c;但是代码写多了&#xff0c;都没有多少让自己吹的时间了。摸鱼时间少是我们太菜了吗&#xff1f;可不要小瞧自己&#xff0c;可能是你没掌握方法。 我自己本身就是一个十分疯狂的工具收集者&#xff0c;收…

实在智能RPA数字员工竞技“宝罗杯”机器人创新总决赛,斩获佳绩!

近日&#xff0c;由中国钢铁工业协会和中国自动化学会指导&#xff0c;中国宝武钢铁集团有限公司主办、宝信软件承办的机器人行业领域的“宝罗杯”机器人创新大赛总决赛在中国宝武上海总部圆满收官。 此次大赛旨在充分凝聚社会智力&#xff0c;聚焦工业机器人的应用场景&#x…

es深度分页原因概念及处理方法

概述 当使用es分页查询的时候&#xff0c;如果查询的数据太靠后了&#xff0c;就会产生深度分页问题。 假设es有3个节点&#xff0c;node1,node2,node3 查询 limti 50000,50 假设请求的是node1,此时会在每个节点上抓出 50050条数据&#xff0c;然后在node1汇总排序&#xff0…

【设计模式】装饰器模式

装饰器模式 以生活中的场景来举例&#xff0c;一个蛋糕胚&#xff0c;给它涂上奶油就变成了奶油蛋糕&#xff0c;再加上巧克力和草莓&#xff0c;它就变成了巧克力草莓蛋糕。 像这样在不改变原有对象的基础之上&#xff0c;将功能附加到原始对象上的设计模式就称为装饰模式(D…

如何查看磁盘空间并挂载磁盘

df -h内容参数含义Filesystem文件系统Size分区大小1k-blocks单位是1KB(使用df查看)Used已用容量Avail还可用的容量Use%已用百分比Mounted on挂载点du -h查看某目录下占用空间最多的文件或目录。取前10个。需要先进入该目录下。du -cks * | sort -rn | head -n 10参数含义-s对每…

腾讯游戏,“迷失”自己

【潮汐商业评论/原创】“那个号我忘记密码了&#xff0c;你等我换个新号跟你玩”。这是Lynn《王者荣耀》双排队友常说的话。因为未成年&#xff0c;账号只有周末能玩&#xff0c;而且只有两小时。所以Lynn的这位网友&#xff0c;经常用家长的手机号注册游戏账号&#xff0c;但是…

Yarn调度器和调度算法

目录 1 先进先出调度器&#xff08;FIFO&#xff09; 2 容量调度器&#xff08;Capacity Scheduler&#xff09; 3 公平调度器&#xff08;Fair Scheduler&#xff09; 缺额&#xff1a; 公平调度器队列资源分配方式 公平调度器资源分配算法 Hadoop作业调度器主要有三种&…

分库分表原理

一、数据库瓶颈 会导致数据库的活跃连接数增加&#xff0c;进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是&#xff0c;可用数据库连接少甚至无连接可用。接下来就可以想象了吧&#xff08;并发量、吞吐量、崩溃&#xff09;。 IO瓶颈-分库和垂直分表…

探索测试的一些总结

1)探索性测试与脚本化测试的主要区别&#xff1a;1)探索性测试将更多更高的认知水平的工作放在测试执行&#xff0c;而脚本化测试则更关注测试设计;2)前者更强调测试活动的并行和相互反馈(学习、设计、执行与结果分析等)&#xff0c;而后者的测试活动是相对串行的。 2)脚本化测…