Mybatis框架源码笔记(七)之Mybatis中类型转换模块(TypeHandler)解析
1、JDBC的基本操作回顾
这里使用伪代码概括一下流程:
-
- 对应数据库版本的驱动包自行下载加载驱动类
(Class.forName("com.mysql.cj.jdbc.Driver"))
-
- 创建Connection连接:
conn = DriverManager.getConnection("jdbc:mysql://数据库IP:port/数据库名称?useUnicode=true&characterEncoding=utf8", "用户名", "用户密码");
-
- 准备SQL语句:
String sql = "select * from lib_book where book_id = ?";
-
- 创建预处理语句对象
PreparedStatement ps = conn.prepareStatement(sql)
-
- 输入参数处理
// 需要根据参数索引位置和参数类型替换对应占位符索引位置上的?为实际的参数值 例如 ps.setInt(1, 12)
ps.setInt(1, 10);
ps.setString(2, "老人与海");
-
- 执行SQL语句
查询操作:
ResultSet resultSet = ps.executeQuery();
修改操作:
ps.execute();
-
- 处理返回结果集
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)); }
-
- 依次关闭打开的所有句柄对象(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 类型 |
---|---|---|
BooleanTypeHandler | java.lang.Boolean, boolean | 数据库兼容的 BOOLEAN |
ByteTypeHandler | java.lang.Byte, byte | 数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler | java.lang.Short, short | 数据库兼容的 NUMERIC 或 SMALLINT |
IntegerTypeHandler | java.lang.Integer, int | 数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler | java.lang.Long, long | 数据库兼容的 NUMERIC 或 BIGINT |
FloatTypeHandler | java.lang.Float, float | 数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler | java.lang.Double, double | 数据库兼容的 NUMERIC 或 DOUBLE |
BigDecimalTypeHandler | java.math.BigDecimal | 数据库兼容的 NUMERIC 或 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[] | 数据库兼容的字节流类型 |
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 | 或未指定类型 |
EnumTypeHandler | Enumeration Type | VARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值) |
EnumOrdinalTypeHandler | Enumeration Type | 任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称)。 |
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 或 LONGVARCHAR |
JapaneseDateTypeHandler | java.time.chrono.JapaneseDate | DATE |
这里通过一个具体的基础数据类型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 输入参数处理
主要涉及的核心类就是SimpleExecutor
、PreparedStatementHandler
和DefaultParameterHandler
,来看看每个类中关于输入参数处理的核心方法。
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框架做了面向对象层次的封装, 其实核心就是上面那三件事
。
PreparedStatementHandler
这里省略了关于handler.parameterize()方法的调用过程的解析, 直接说明一下吧, 就是运用了模板方法模式
完成了StatementHandler的参数替换的功能,因为MappedStatement的StatementType
默认的类型是PREPARED
, 所以这里调用的是
PreparedStatementHandler
的parameterize()
方法,接下来看看真正处理输入参数
的方法
@Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}
上面的方法调用了ParameterHandler
接口的setParameters()方法
3.3.2 ParameterHandler对象是什么时候创建的?
在SimpleExecutor对象的doQuery()或者doUpdate()方法中调用了创建StatementHanlder
、ParameterHandler
、ResultSetHandler
对象的入口,方法如下
@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 输出结果处理
主要涉及的核心类就是SimpleExecutor
、PreparedStatementHandler
和DefaultResultSetHandler
,来看看每个类中关于查询结果集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》
论文地址:https://arxiv.org/pdf/2202.05263.pdf 复现源码:https://github.com/dvlab-research/BlockNeRFPytorch 概述 Block-NeRF是一种能够表示大规模环境的神经辐射场(Neural Radiance Fields)的变体,将 NeRF 扩展到…...

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

圆桌(满足客人空座需求,合理安排客人入座圆桌,准备最少的椅子)
CSDN周赛第30期第四题算法解析。 (本文获得CSDN质量评分【91】)【学习的细节是欢悦的历程】Python 官网:https://www.python.org/ Free:大咖免费“圣经”教程《 python 完全自学教程》,不仅仅是基础那么简单…… 地址:https://lq…...
如何入门大数据?
我们首先了解一下大数据到底是什么~ 大数据开发做什么? 大数据开发分两类,编写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…...

社科院与杜兰大学金融管理硕士项目——人生没有太晚的开始,不要过早的放弃
经常听到有人问,“我都快40了,现在学车晚不晚呢”“现在考研晚不晚?”“学画画晚不晚?”提出这些疑问的人,往往存在拖延,想法只停留在想的阶段,从来不去行动。当看到周边行动起来的人开始享受成…...

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

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

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

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

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

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

手把手教你实现书上的队列,进来试试?
一.队列的基本概念队列的定义队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允…...

【springboot】springboot介绍
学习资料 SpringBoot 语雀 (yuque.com)【尚硅谷】SpringBoot2零基础入门教程(spring boot2干货满满)_哔哩哔哩_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,设置导入选项 然后点击ok 如果destination选择当前的workspace,那么导入完成之后如下: (推荐使用…...

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

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

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...

跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

Tauri2学习笔记
教程地址:https://www.bilibili.com/video/BV1Ca411N7mF?spm_id_from333.788.player.switch&vd_source707ec8983cc32e6e065d5496a7f79ee6 官方指引:https://tauri.app/zh-cn/start/ 目前Tauri2的教程视频不多,我按照Tauri1的教程来学习&…...
6.计算机网络核心知识点精要手册
计算机网络核心知识点精要手册 1.协议基础篇 网络协议三要素 语法:数据与控制信息的结构或格式,如同语言中的语法规则语义:控制信息的具体含义和响应方式,规定通信双方"说什么"同步:事件执行的顺序与时序…...