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

17 篇文章 0 订阅
8 篇文章 0 订阅

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);
    }
  }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis 字典转换 TypeHandler 是一种用于处理数据库字段和 Java 实体属性之间的转换的机制。它允许开发人员在数据库存储枚举值或其他常量,并在 MyBatis 查询时将其转换为相应的 Java 对象。 通常,在数据库设计,我们会将某些字段的取值限制为预定义的有限选项,通常以整数或字符串形式存储在数据库。然而,在 Java 代码,我们更倾向于使用枚举类型或其他自定义对象来表示这些选项。 为了解决数据库字段和 Java 对象之间的转换问题,MyBatis 提供了 TypeHandler 接口。该接口定义了将字段值转换为 Java 对象和将 Java 对象转换为字段值的方法。开发人员可以根据自己的需求实现该接口并注册自己的 TypeHandler。 当 MyBatis 执行查询时,如果遇到了定义了 TypeHandler 的字段,它将使用相应的 TypeHandler 对象来处理字段的转换。类型转换可以是双向的,也就是说可以将 Java 对象转换为数据库字段,以及将数据库字段转换为 Java 对象。 字典转换的一个典型应用场景是将数据库的整数值转换为对应的枚举类型。通过实现自定义的 TypeHandler,开发人员可以将数据库的整数字段映射为相应的枚举对象,从而在程序更方便地使用枚举值。这种转换可以在查询结果映射时自动进行,也可以在参数设置时手动进行。 总之,MyBatis 字典转换 TypeHandler 是一项非常实用的功能,它允许我们在数据库Java 对象之间进行灵活的转换,使程序开发更加方便和高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值