1. Review of basic operations of JDBC
Here is a summary of the process using pseudocode:
-
- Download the driver package corresponding to the database version and load the driver class by yourself.
(Class.forName("com.mysql.cj.jdbc.Driver"))
-
- Create Connection connection:
conn = DriverManager.getConnection("jdbc:mysql://数据库IP:port/数据库名称?useUnicode=true&characterEncoding=utf8", "用户名", "用户密码");
-
- Prepare SQL statement:
String sql = "select * from lib_book where book_id = ?";
-
- Create prepared statement objects
PreparedStatement ps = conn.prepareStatement(sql)
-
- Input parameter processing
// 需要根据参数索引位置和参数类型替换对应占位符索引位置上的?为实际的参数值 例如 ps.setInt(1, 12)
ps.setInt(1, 10);
ps.setString(2, "老人与海");
-
- Execute SQL statement
Query operation:
ResultSet resultSet = ps.executeQuery();
Modification operation:
ps.execute();
-
- 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)); }
-
- 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
3.2 Design and implementation of Mybatis
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;
}
3.2.2 BaseTypeHandler implementation class
This basic implementation class only provides basic implementation of common type conversion, and extends the original functions.
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 typeBooleanTypeHandler
class
/*
* 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
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.
The core methods in the alias register are as follows:
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?
It’s just that the Mybatis framework has encapsulated the object-oriented level. In fact, the core is the above a>三件事
.
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 StatementType
The 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
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:
Look at the construction method of RoutingStatementHandler, as follows:
Look again< a i=3>, as follows:BaseStatementHandler的构造方法
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;
}
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);
}
}