Mybatis framework source code notes (7) Analysis of the type conversion module (TypeHandler) in Mybatis

1. Review of basic operations of JDBC

Here is a summary of the process using pseudocode:

    1. Download the driver package corresponding to the database version and load the driver class by yourself.
(Class.forName("com.mysql.cj.jdbc.Driver"))
    1. Create Connection connection:
conn = DriverManager.getConnection("jdbc:mysql://数据库IP:port/数据库名称?useUnicode=true&characterEncoding=utf8", "用户名", "用户密码");
    1. Prepare SQL statement:
String sql = "select * from lib_book where book_id = ?";
    1. Create prepared statement objects
PreparedStatement ps = conn.prepareStatement(sql)
    1. Input parameter processing
// 需要根据参数索引位置和参数类型替换对应占位符索引位置上的?为实际的参数值 例如 ps.setInt(1, 12)
ps.setInt(1, 10);
ps.setString(2, "老人与海");
    1. Execute SQL statement

Query operation:

ResultSet resultSet = ps.executeQuery();

Modification operation:

ps.execute();
    1. Process the returned result set
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. Close all open handle objects (ResultSet, PreparedStatement, Connection) in sequence
 if(statement != null) {
    
    
     try {
    
    
         statement.close();
     } catch (SQLException e) {
    
    
         e.printStackTrace();
     }
 }

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

2. Myabtis framework simplifies JDBC operations

Summarize the usage process of Mybatis framework:

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

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

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

Unit test usage process:

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();

Comparing operations, we found that after using the Mybatis framework, we no longer need to pay attention to the specific operation process of JDBC. The mapping conversion of input parameters and return results
does not require us to handle it manually. Now, we no longer need to manually handle the release of resources. We only need to call a simple application layer interface to complete all operations. It's crazy.

But for a qualified developer, we must be proficient in using the API of the framework and have a clear understanding of its principles, so that when problems arise again, we can modify and extend the original framework to complete the functions we want. I am getting too far ahead. , Today we will mainly talk about how the Mybatis framework encapsulates the type conversion function of processing input parameters and returning result sets in JDBC?

3. Type conversion module of Myabtis framework

The type conversion module belongs to the basic support layer module of the Mybatis framework. It mainly implements 数据库中数据 and , < /span> It is mainly used in the following two scenarios: Java对象中的属性双向映射

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

3.1 How to complete type conversion between javaType and JDBCType

Insert image description here

3.2 Design and implementation of Mybatis

Insert image description here

3.2.1 TypeHandler interface

All type converter implementation classes provided in the MyBatis framework inherit the TypeHandler interface, which defines the most basic functions of the type converter (processing input parameters and output parameters).

/*
 *    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;

}

Insert image description here

3.2.2 BaseTypeHandler implementation class

This basic implementation class only provides basic implementation of common type conversion, and extends the original functions.
Insert image description here
However, the conversion of different data types still needs to be processed according to the type, such as numeric type, string type, date type, etc. The processing methods are definitely different. The mybatis framework provides these Commonly used types of converter implementation

type handler Java types JDBC type
BooleanTypeHandler java.lang.Boolean, boolean DATABASE COMPATIBLE BOOLEAN
ByteTypeHandler java.lang.Byte, byte Database compatible NUMERIC or BYTE
ShortTypeHandler java.lang.Short, short Database compatible NUMERIC or SMALLINT
IntegerTypeHandler java.lang.Integer, int Database compatible NUMERIC or INTEGER
LongTypeHandler java.lang.Long, long Database compatible NUMERIC or BIGINT
FloatTypeHandler java.lang.Float, float Database compatible NUMERIC or FLOAT
DoubleTypeHandler java.lang.Double, double Database compatible NUMERIC or DOUBLE
BigDecimalTypeHandler java.math.BigDecimal Database compatible NUMERIC or DECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR
ClobReaderTypeHandler java.io.Reader -
ClobTypeHandler java.lang.String CLOB, LONGVARCHAR
NStringTypeHandler java.lang.String NVARCHAR, NCHAR
NClobTypeHandler java.lang.String NCLOB
BlobInputStreamTypeHandler java.io.InputStream -
ByteArrayTypeHandler byte[] Database compatible byte stream types
BlobTypeHandler byte[] BLOB, LONGVARBINARY
DateTypeHandler java.util.Date TIMESTAMP
DateOnlyTypeHandler java.util.Date DATE
TimeOnlyTypeHandler java.util.Date TIME
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
SqlDateTypeHandler java.sql.Date DATE
SqlTimeTypeHandler java.sql.Time TIME
ObjectTypeHandler Any OTHER or unspecified type
EnumTypeHandler Enumeration Type VARCHAR or any compatible string type used to store the name of the enumeration (rather than the index ordinal value)
EnumOrdinalTypeHandler Enumeration Type Any compatible NUMERIC or DOUBLE type used to store the ordinal value (rather than the name) of an enumeration.
SqlxmlTypeHandler java.lang.String SQLXML
InstantTypeHandler java.time.Instant TIMESTAMP
LocalDateTimeTypeHandler java.time.LocalDateTime TIMESTAMP
LocalDateTypeHandler java.time.LocalDate DATE
LocalTimeTypeHandler java.time.LocalTime TIME
OffsetDateTimeTypeHandler java.time.OffsetDateTime TIMESTAMP
OffsetTimeTypeHandler java.time.OffsetTime TIME
ZonedDateTimeTypeHandler java.time.ZonedDateTime TIMESTAMP
YearTypeHandler java.time.Year INTEGER
MonthTypeHandler java.time.Month INTEGER
YearMonthTypeHandler java.time.YearMonth VARCHAR or LONGVARCHAR
JapaneseDateTypeHandler java.time.chrono.JapaneseDate DATE

This is explained through a specific basic data typeBooleanTypeHandlerclass

/*
 *    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 register

The Mybatis framework provides a lot of type converter implementations for us to use. These type converter objects definitely do not need to be created by ourselves. They must have been completed when Mybatis is integrated into our projectIn the constructor of the TypeHandlerRegistry register, the creation and registration of TypeHandler class objects that are needed when converting common data types in Java and corresponding data types in jdbc are completed type converter. is accomplished through the
is instantiated by the default type converter, so how does the Myabtis framework save these objects? When were the instantiation operations of these objects completed?
TypeHandlerRegistry
Insert image description here
Insert image description here

3.2.4 TypeAliasRegistry alias register

Aliases are often used when applying the MyBatis framework, which can greatly simplify our code. In fact, in MyBatis, they are managed through the TypeAliasRegistry class. In the constructor of the TypeAliasRegistry class, aliases of common types in the system are injected.
Insert image description here
The core methods in the alias register are as follows:
Insert image description here

  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);
  }

Two ways to register Mybatis custom alias:

  • The first configuration file method (mybatis-config.xml)
<typeAliases>
	<package name="com.baidu"/>
</typeAliases> 
  • Second annotation method @Alias
@Alias("People")
public class SysPeople {
    
    
	private String name;
}

Custom alias registration is also performed through the registerAliases(String packageName, Class<?> superType) method.

  /**
   * 通过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 How to customize the type changer

You can override existing type handlers or create your own to handle unsupported or nonstandard types. The specific method is:
Implement the org.apache.ibatis.type.TypeHandler interface,
or inherit a very convenient class org.apache.ibatis.type.BaseTypeHandler , and can (optionally) be mapped to a JDBC type.
The official sample code for specific examples has been provided. If you are interested, you can view the document yourself. Post the portal here: Custom type converter document and sample code address: https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers

3.3 Application of Typehandler in Mybatis

Regardless of the Mybatis framework or the execution process of SQL statements, after such a long period of contact, everyone must be more and more familiar with it. Everyone should be able to conditionally reflect on the conversion of SQL statement input parameters and the processing process nodes of SQL statement execution results. Speaking of which, it must be processed when the SQL statement is executed. The execution of SQL statements in the Myabtis framework relies on Executor to complete.

The parameter processing of SQL and the processing of ResultSet must also be completed in an implementation class of this interface. I won’t be too careful here and just show my sword.

3.3.1 Input parameter processing

The core classes mainly involved in are SimpleExecutor, PreparedStatementHandler and DefaultParameterHandler. Let’s take a look at the input information in each class. The core method of parameter processing.

  • SimpleExecutor

The core methods of SQL statement preprocessing are as follows

  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语句中有查询条件,使用了参数占位符, 就将实际的参数替换成真实的输入参数

Look at the above code, doesn’t it correspond to the code in JDBC, right?
Insert image description here
It’s just that the Mybatis framework has encapsulated the object-oriented level. In fact, the core is the above a>三件事.
SimpleExecutor

  • PreparedStatementHandler

The analysis of the calling process of the handler.parameterize() method is omitted here. Let’s explain it directly. It uses模板方法模式 to complete the parameter replacement function of StatementHandler, because MappedStatement StatementTypeThe default type is PREPARED, so what is called here is the method of
PreparedStatementHandler, then Let’s take a look at the actual method of handlingparameterize()输入参数

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

The above method calls the setParameters() method of the ParameterHandler interface
Insert image description here

3.3.2 When is the ParameterHandler object created?

Creation was called in the doQuery() or doUpdate() method of the SimpleExecutor objectStatementHanlder, ParameterHandler, ResultSetHandler The entrance to the object, the method is as follows

  @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);
    }
  }

Let’s take a lookconfiguration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);This method

 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;
  }

Look at the construction method of RoutingStatementHandler, as follows:
Insert image description here
Look at the construction method of RoutingStatementHandler, as follows:
Insert image description here
Look again< a i=3>, as follows:BaseStatementHandler的构造方法
Insert image description here

Conclusion

在创建实际Statement对象对应的StatementHandler时, 完成了ParameterHandler和ResultHandler对象的创建。
3.3.3 Who is the ParameterHandler/ResultSethandler object?

Continuing with the above discussion, let’s take a look at the execution process of the following two lines of code in 3.3.2 BaseStatementHandler的构造方法

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

The corresponding methods in the Configuration class are as follows:

  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;
  }

Insert image description here
Conclusion

如果没有自定义插件, 那么Mybatis框架会为我们创建框架提供的默认的DefaultParameterHandler和DefaultResultHandler对象来实现
SQL语句入参和查询结果集ResultSet转换的功能
3.3.4 Method implementation of parameter replacement in 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 Output result processing

The core classes mainly involved in are SimpleExecutor, PreparedStatementHandler and DefaultResultSetHandler. Let’s take a look at the queries in each class. The core method of ResultSet processing.

  • 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

Call the resultSetHandler.handleResultSets(ps) method to process the query result set 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 Method implementation of ResultSet result set conversion in DefaultResultSetHandler

handleResultSets() method

  //
  // 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);
  }

Method to process each row of data and convert it from ResultSet to java entity class

 //
  // 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;
  }

The value of the corresponding type can be returned according to the corresponding TypeHandler during automatic mapping.

  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;
  }

Call the corresponding TypeHandler according to the Property to return the value of the corresponding type.

  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);
    }
  }

Guess you like

Origin blog.csdn.net/qq_41865652/article/details/129418119