当前位置: 首页 > news >正文

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

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里面处理了)*/@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType)throws SQLException {ps.setBoolean(i, parameter);}/*** ResultSet结果集里面jdbcType返回为null时通过调用该方法"传递列名方式"转换成Java类中对应属性的javaType*/@Overridepublic Boolean getNullableResult(ResultSet rs, String columnName)throws SQLException {boolean result = rs.getBoolean(columnName);return !result && rs.wasNull() ? null : result;}/*** ResultSet结果集里面jdbcType返回为null时通过调用该方法"传递列索引方式"转换成Java类中对应属性的javaType*/@Overridepublic Boolean getNullableResult(ResultSet rs, int columnIndex)throws SQLException {boolean result = rs.getBoolean(columnIndex);return !result && rs.wasNull() ? null : result;}/*** 存储过程执行结果转换成javaType类型*/@Overridepublic 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 #6if (!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()方法,接下来看看真正处理输入参数的方法

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

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

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

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

  @Overridepublic <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语句中的占位符为实际传入的参数值的方法*/@Overridepublic 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 paramsvalue = 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
  @Overridepublic <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

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

相关文章:

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

1、JDBC的基本操作回顾 这里使用伪代码概括一下流程: 对应数据库版本的驱动包自行下载加载驱动类 (Class.forName("com.mysql.cj.jdbc.Driver"))创建Connection连接: conn DriverManager.getConnection("jdbc:mysql://数据库IP:port/数据库名称?useUnico…...

论文阅读《Block-NeRF: Scalable Large Scene Neural View Synthesis》

论文地址&#xff1a;https://arxiv.org/pdf/2202.05263.pdf 复现源码&#xff1a;https://github.com/dvlab-research/BlockNeRFPytorch 概述 Block-NeRF是一种能够表示大规模环境的神经辐射场&#xff08;Neural Radiance Fields&#xff09;的变体&#xff0c;将 NeRF 扩展到…...

【Matlab】如何设置多个y轴

MTALAB提供了创建具有两个y轴的图&#xff0c;通过help yyaxis就能看到详细的使用方式。 但是如果要实现3个及以上y轴的图&#xff0c;就没有现成的公式使用了&#xff0c;如下图所示。 具体代码 % 数据准备 x10:0.01:10; y1sin(x1); x20:0.01:10; y2cos(x2); x30:0.01:10;…...

圆桌(满足客人空座需求,合理安排客人入座圆桌,准备最少的椅子)

CSDN周赛第30期第四题算法解析。 (本文获得CSDN质量评分【91】)【学习的细节是欢悦的历程】Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单…… 地址&#xff1a;https://lq…...

如何入门大数据?

我们首先了解一下大数据到底是什么~ 大数据开发做什么&#xff1f; 大数据开发分两类&#xff0c;编写Hadoop、Spark的应用程序和对大数据处理系统本身进行开发。 大数据开发工程师主要负责公司大数据平台的开发和维护、相关工具平台的架构设计与产品开发、网络日志大数据分…...

如何在Vite项目中使用Lint保证代码质量

通常,大型前端项目都是多人参与的,由于开发者的编码习惯和喜好都不尽相同,为了降低维护成本,提高代码质量,所以需要专门的工具来进行约束,并且可以配合一些自动化工具进行检查,这种专门的工具称为Lint,可能大家接触得最多就是ESLint。 对于实现自动化代码规范检查及修…...

Spark高手之路1—Spark简介

文章目录Spark 概述1. Spark 是什么2. Spark与Hadoop比较2.1 从时间节点上来看2.2 从功能上来看3. Spark Or Hadoop4. Spark4.1 速度快4.2 易用4.3 通用4.4 兼容5. Spark 核心模块5.1 Spark-Core 和 弹性分布式数据集(RDDs)5.2 Spark SQL5.3 Spark Streaming5.4 Spark MLlib5.5…...

社科院与杜兰大学金融管理硕士项目——人生没有太晚的开始,不要过早的放弃

经常听到有人问&#xff0c;“我都快40了&#xff0c;现在学车晚不晚呢”“现在考研晚不晚&#xff1f;”“学画画晚不晚&#xff1f;”提出这些疑问的人&#xff0c;往往存在拖延&#xff0c;想法只停留在想的阶段&#xff0c;从来不去行动。当看到周边行动起来的人开始享受成…...

Spatial-Temporal Graph ODE Networks for Traffic Flow Forecasting

Spatial-Temporal Graph ODE Networks for Traffic Flow Forecasting 摘要 交通流量的复杂性和长范围时空相关性是难点 经典现存的工作&#xff1a; 1.利用浅图神经网络&#xff08;shallow graph convolution networks&#xff09;和 时间提取模块去分别建模空间和时间依赖…...

IP协议+以太网协议

在计算机网络体系结构的五层协议中&#xff0c;第三层就是负责建立网络连接&#xff0c;同时为上层提供服务的一层&#xff0c;网络层协议主要负责两件事&#xff1a;即地址管理和路由选择&#xff0c;下面就网络层的重点协议做简单介绍~~ IP协议 网际协议IP是TCP/IP体系中两…...

可视化组件届的仙女‖蝴蝶结图、玫瑰环图、小提琴图

在上一篇内容中为大家介绍了几个堪称可视化组件届吴彦祖的高级可视化图表。既然帅哥有了&#xff0c;怎么能少得了美女呢&#xff1f;今天就为大家介绍几个可视化组件届的“美女姐姐”&#xff0c;说一句是组件届的刘亦菲不为过。蝴蝶结图蝴蝶结图因其形似蝴蝶结而得名&#xf…...

人的高级认知:位置感

你知道吗&#xff1f;人有个高级认知&#xff1a;位置感 位置感是啥&#xff1f;咋提高位置感&#xff1f; 趣讲大白话&#xff1a;知道自己几斤几两 【趣讲信息科技99期】 ******************************* 位置感 就是对自己所处环境和自身存在的领悟 属于人生智慧 来源于阅历…...

MATLAB——信号的采样与恢复

**题目&#xff1a;**已知一个连续时间信号 其中&#xff1a;f01HZ&#xff0c;取最高有限带宽频率fm5f0。分别显示原连续时间信号波形和 3种情况下抽样信号的波形。并画出它们的幅频特性曲线&#xff0c;并对采样后的信号进行恢复。 step1.绘制出采样信号 这部分相对简单…...

Docker Nginx 反向代理

最近在系统性梳理网关的知识&#xff0c;其中网关的的功能有一个是代理&#xff0c;正好咱们常用的Nginx也具备次功能&#xff0c;今天正好使用Nginx实现一下反向代理&#xff0c;与后面网关的代理做一个对比&#xff0c;因为我使用的docker安装的Nginx&#xff0c;与直接部署N…...

手把手教你实现书上的队列,进来试试?

一.队列的基本概念队列的定义队列&#xff08;queue&#xff09;是只允许在一端进行插入操作&#xff0c;而在另一端进行删除操作的线性表。队列是一种先进先出&#xff08;First In First Out&#xff09;的线性表&#xff0c;简称FIFO。允许插入的一端称为队尾&#xff0c;允…...

【springboot】springboot介绍

学习资料 SpringBoot 语雀 (yuque.com)【尚硅谷】SpringBoot2零基础入门教程&#xff08;spring boot2干货满满&#xff09;_哔哩哔哩_bilibiliSpringBoot2核心技术与响应式编程: SpringBoot2核心技术与响应式编程 (gitee.com) Spring 和Springboot 1、Spring能做什么 1.1…...

PMP项目管理项目整合管理

目录1 项目整合管理概述2 制定项目章程3 制定项目管理计划4 指导与管理项目工作5 管理项目知识6 监控项目工作7 实施整体变更控制8 结束项目或阶段1 项目整合管理概述 项目整合管理包括对隶属于项目管理过程组的各种过程和项目管理活动进行识别、定义、组合、统一和协调的各个…...

ADS中导入SPICE模型

这里写目录标题在官网中下载SPICE模型ADS中导入SPICE模型在官网中下载SPICE模型 英飞凌官网 ADS中导入SPICE模型 点击option&#xff0c;设置导入选项 然后点击ok 如果destination选择当前的workspace&#xff0c;那么导入完成之后如下&#xff1a; &#xff08;推荐使用…...

C++:异常

在学习异常之前&#xff0c;来简单总结一下传统的处理错误的方式&#xff1a; 1. 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以接受。如发生内存错误&#xff0c;除0错误时就会终止程序。 2. 返回错误码&#xff0c;缺陷&#xff1a;需要程序员自己去查找…...

3.初识Vue

目录 1 vue 浏览器调试工具 1.1 安装 1.2 配置 2 数据驱动视图与双向数据绑定 3 简单使用 3.1 下载 3.2 将信息渲染到DOM上 4 使用vue浏览器调试工具 5 vue指令 1 vue 浏览器调试工具 chrome可能是我浏览器的原因&#xff0c;装上用不了&#xff0c;我们使…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...