文章目录
- 一、目标:结果集处理器
- 二、设计:结果集处理器
- 三、实现:结果集处理器
- 3.1 工程结构
- 3.2 结果集处理器关系图
- 3.3 出参参数处理
- 3.3.1 结果映射Map
- 3.3.2 结果映射封装
- 3.3.3 修改映射器语句类
- 3.3.4 映射构建器助手
- 3.3.5 语句构建器调用助手类
- 3.3.6 映射构建器调用助手类
- 3.4 类型处理器
- 3.4.1 类型处理器接口
- 3.4.2 类型处理器的基类
- 3.4.3 具体子类类型处理器
- 3.5 添加分页记录限制配置
- 3.5.1 分页记录限制
- 3.5.2 修改执行器接口
- 3.5.3 修改执行器抽象基类
- 3.5.4 修改简单执行器
- 3.5.5 修改语句处理器抽象基类
- 3.5.6 修改预处理语句处理器
- 3.5.7 修改简单语句处理器
- 3.5.8 修该配置项
- 3.5.9 修改默认SqlSession实现类
- 3.6 结果集处理器
- 3.6.1 结果上下文接口
- 3.6.2 默认结果上下文实现类
- 3.6.3 结果集处理器
- 3.6.4 默认结果集处理器
- 3.7 创建对象,属性填充
- 3.7.1 结果集包装器
- 3.7.2 默认Map结果处理器
- 四、测试:结果集处理器
- 五、总结:结果集处理器
一、目标:结果集处理器
💡 对执行完查询的结果进行封装处理。
- 之前章节中,我们硬编码的判断封装,这种方式既不能满足不同类型的优雅扩展,也不利于维护迭代。
- 对于结果集的封装处理,其实核心在于我们拿到了 Mapper XML 中所配置的返回类型,解析后把从数据库查询到的结果,反射到类型实例化的对象上。
- 那么这个过程中,我们需要满足不同返回类型的处理,如
Integer、Long、String、Date
等,都要一一与数据库的类型匹配。- 于此同时,返回的结果可能是一个普通的基本类型,也可能是我们封装后的对象类型。并这个结果查询也不一定只是一条记录,还可能是多条记录。
- 那么为了更好的处理这些不同情况下的问题,需要对流程进行分治和实现,以及在过程中进行抽象化的解耦,才能满足我们把不同的返回信息诉求,封装到对象离去。
- 分治、抽象、知识:来自于人月神话中的康威定律,它是系统设计的第一原则。
二、设计:结果集处理器
💡 结果集处理器怎么设计?
- 使用 JDBC 获取到查询结果
ResultSet#getObject
可以获取返回属性值,但其实 ResultSet 是可以按照不同的属性类型进行返回结果的,而不是都返回 Object 对象。 - 在上一章处理属性信息时,所开发的 TypeHandler 接口的实现类,就需要扩充返回结果的方法。
- 例如:
LongTypeHandler#getResult、StringTypeHandler#getResult
等。 - 这样我们就可以使用 策略模式 定位到返回的结果,而不需要 if 判断处理。
- 例如:
- 通过解析 XML 信息时封装返回类型到映射器语句类中,
MappedStatement#resultMaps
直到执行完 SQL 语句。 - 按照我们的返回结果参数类型,创建对象和使用 MetaObject 反射工具类填充属性信息。
- 首先我们在解析 XML 语句解析构建器中,添加一个 MapperBuilderAssistant 映射器的助手类,方便我们对参数的统一包装处理,按照职责归属的方式进行细分解耦。
- 通过这样的方式在
MapperBuilderAssistant#setStatementResultMap
中封装返回结果信息,一般来说我们使用 Mybatis 配置返回对象的时候 ResultType 就能解决大部分问题,而不需要都是配置一个 ResultMap 映射结果。 - 但这里的设计其实是把 ResultType 也按照一个 ResultMap 的方式进行封装处理,这样统一一个标准的方式进行包装,做到了适配的效果,也更加方便后面对这样的参数进行统一使用。
- 通过这样的方式在
- 接下来就是执行 JDBC 操作查询到数据以后,对结果的封装。那么在 DefaultResultSetHandler 返回结果处理中。
- 首先先按照我们已经解析到的 ResultType 进行对象的实例化。
- 实例化对象以后再根据解析出来对象中参数的名称获取对应的类型。
- 再根据类型找到 TypeHandler 接口实现类,也就是
LongTypeHandler、StringTypeHandler
,因为通过这样的方式,可以避免 if…else 的判断,而是直接 O(1) 时间复杂度定位到对应的类型处理器,在不同的类型处理器中返回结果信息。 - 最终拿到结果再通过前面章节已经开发过的 MetaObject 反射工具类进行属性信息的设置。
metaObject.setValue(property, value)
最终填充实例化并设置了属性内容的结果对象到上下文中,直至处理完成返回最终的结果数据,以此处理完成。
三、实现:结果集处理器
3.1 工程结构
mybatis-step-10
|-src
|-main
| |-java
| |-com.lino.mybatis
| |-binding
| | |-MapperMethod.java
| | |-MapperProxy.java
| | |-MapperProxyFactory.java
| | |-MapperRegistry.java
| |-builder
| | |-xml
| | | |-XMLConfigBuilder.java
| | | |-XMLMapperBuilder.java
| | | |-XMLStatementBuilder.java
| | |-BaseBuilder.java
| | |-MapperBuilderAssistant.java
| | |-ParameterExpression.java
| | |-SqlSourceBuilder.java
| | |-StaticSqlSource.java
| |-datasource
| | |-druid
| | | |-DruidDataSourceFacroty.java
| | |-pooled
| | | |-PooledConnection.java
| | | |-PooledDataSource.java
| | | |-PooledDataSourceFacroty.java
| | | |-PoolState.java
| | |-unpooled
| | | |-UnpooledDataSource.java
| | | |-UnpooledDataSourceFacroty.java
| | |-DataSourceFactory.java
| |-executor
| | |-parameter
| | | |-ParameterHandler.java
| | |-result
| | | |-DefaultResultContext.java
| | | |-DefaultResultHandler.java
| | |-resultset
| | | |-DefaultResultSetHandler.java
| | | |-ResultSetHandler.java
| | | |-ResultSetWrapper.java
| | |-statement
| | | |-BaseStatementHandler.java
| | | |-PreparedStatementHandler.java
| | | |-SimpleStatementHandler.java
| | | |-StatementHandler.java
| | |-BaseExecutor.java
| | |-Executor.java
| | |-SimpleExecutor.java
| |-io
| | |-Resources.java
| |-mapping
| | |-BoundSql.java
| | |-Environment.java
| | |-MappedStatement.java
| | |-ParameterMapping.java
| | |-ResultMap.java
| | |-ResultMapping.java
| | |-SqlCommandType.java
| | |-SqlSource.java
| |-parsing
| | |-GenericTokenParser.java
| | |-TokenHandler.java
| |-reflection
| | |-factory
| | | |-DefaultObjectFactory.java
| | | |-ObjectFactory.java
| | |-invoker
| | | |-GetFieldInvoker.java
| | | |-Invoker.java
| | | |-MethodInvoker.java
| | | |-SetFieldInvoker.java
| | |-property
| | | |-PropertyNamer.java
| | | |-PropertyTokenizer.java
| | |-wrapper
| | | |-BaseWrapper.java
| | | |-BeanWrapper.java
| | | |-CollectionWrapper.java
| | | |-DefaultObjectWrapperFactory.java
| | | |-MapWrapper.java
| | | |-ObjectWrapper.java
| | | |-ObjectWrapperFactory.java
| | |-MetaClass.java
| | |-MetaObject.java
| | |-Reflector.java
| | |-SystemMetaObject.java
| |-scripting
| | |-defaults
| | | |-DefaultParameterHandler.java
| | | |-RawSqlSource.java
| | |-xmltags
| | | |-DynamicContext.java
| | | |-MixedSqlNode.java
| | | |-SqlNode.java
| | | |-StaticTextSqlNode.java
| | | |-XMLLanguageDriver.java
| | | |-XMLScriptBuilder.java
| | |-LanguageDriver.java
| | |-LanguageDriverRegistry.java
| |-session
| | |-defaults
| | | |-DefaultSqlSession.java
| | | |-DefaultSqlSessionFactory.java
| | |-Configuration.java
| | |-ResultContext.java
| | |-ResultHandler.java
| | |-RowBounds.java
| | |-SqlSession.java
| | |-SqlSessionFactory.java
| | |-SqlSessionFactoryBuilder.java
| | |-TransactionIsolationLevel.java
| |-transaction
| | |-jdbc
| | | |-JdbcTransaction.java
| | | |-JdbcTransactionFactory.java
| | |-Transaction.java
| | |-TransactionFactory.java
| |-type
| | |-BaseTypeHandler.java
| | |-IntegerTypeHandler.java
| | |-JdbcType.java
| | |-LongTypeHandler.java
| | |-StringTypeHandler.java
| | |-TypeAliasRegistry.java
| | |-TypeHandler.java
| | |-TypeHandlerRegistry.java
|-test
|-java
| |-com.lino.mybatis.test
| |-dao
| | |-IUserDao.java
| |-po
| | |-User.java
| |-ApiTest.java
|-resources
|-mapper
| |-User_Mapper.xml
|-mybatis-config-datasource.xml
3.2 结果集处理器关系图
- 在 XML 语句构建器中使用映射构建器助手,包装映射器语句入参、出参的封装处理。通过此功能职责的切割,满足不同逻辑单元的扩展。
MapperBuilderAssistant#setStatementResultMap
处理ResultType/ResultMap
的封装信息。
- 入参信息的解析会存放到映射语句 MapperStatement 类中,这样随着
DefaultSqlSession#selectOne
具体方法的执行时,就可以通过 statement 从配置项中获取到对应的 MappedStatement 信息,所以这里的设计符合一个充血模型结构的领域功能聚合。 - 最后就是实现了 ResultSetHandler 结果集处理器接口的 DefaultResultSetHandler 实现类,对查询结果的封装处理。
- 按照解析出来的 resultType 类型进行实例化对象,之后根据对象的属性信息寻找对应的处理策略,避免 if…else 判断的方式获取对应的结果。
- 当对象和属性都准备完毕后,就可以使用 MetaObject 元对象反射工具类进行属性填充,形成一个完整的结果对象,并写入到结果上下文中 DefaultResultContext 返回。
3.3 出参参数处理
- 鉴于对 XML 语句构建器中解析语句后的信息封装会逐步增多,这里需要引入映射构建器助手对类中方法的职责进行划分,降低一个方法块内的逻辑复杂度。
3.3.1 结果映射Map
- 在 Mybatis 中,在一条语句配置中需要有包括一个返回类型的配置,这个返回类型可以是通过 resultType 配置,也可以使用 resultMap 进行处理,而无论使用哪种方式其实最终都会被封装成统一的 ResultMap 结果映射类。
- 在配置类 ResultMap 中都配置了字段的映射,所以实际在 ResultMap 中还会包含 ResultMapping 。
- 也就是每一个字段的映射信息。包括:
colum、javaType、jdbcType
等
- 也就是每一个字段的映射信息。包括:
ResultMapping.java
package com.lino.mybatis.mapping;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.JdbcType;
import com.lino.mybatis.type.TypeHandler;
/**
* @description: 结果映射Map
*/
public class ResultMapping {
private Configuration configuration;
private String property;
private String column;
private Class<?> javaType;
private JdbcType jdbcType;
private TypeHandler<?> typeHandler;
public ResultMapping() {
}
public static class Builder {
private ResultMapping resultMapping = new ResultMapping();
}
}
3.3.2 结果映射封装
ResultMap.java
package com.lino.mybatis.mapping;
import com.lino.mybatis.session.Configuration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @description: 结果映射
*/
public class ResultMap {
private String id;
private Class<?> type;
private List<ResultMapping> resultMappings;
private Set<String> mappedColumns;
public ResultMap() {
}
public static class Builder {
private ResultMap resultMap = new ResultMap();
public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
resultMap.id = id;
resultMap.type = type;
resultMap.resultMappings = resultMappings;
}
public ResultMap build() {
resultMap.mappedColumns = new HashSet<>();
return resultMap;
}
}
public String getId() {
return id;
}
public Class<?> getType() {
return type;
}
public List<ResultMapping> getResultMappings() {
return resultMappings;
}
public Set<String> getMappedColumns() {
return mappedColumns;
}
}
3.3.3 修改映射器语句类
MappedStatement.java
package com.lino.mybatis.mapping;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import java.util.List;
/**
* @description: 映射器语句类
*/
public class MappedStatement {
private Configuration configuration;
private String id;
private SqlCommandType sqlCommandType;
private SqlSource sqlSource;
Class<?> resultType;
private LanguageDriver lang;
private List<ResultMap> resultMaps;
public MappedStatement() {
}
public static class Builder {
private MappedStatement mappedStatement = new MappedStatement();
public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, SqlSource sqlSource, Class<?> resultType) {
mappedStatement.configuration = configuration;
mappedStatement.id = id;
mappedStatement.sqlCommandType = sqlCommandType;
mappedStatement.sqlSource = sqlSource;
mappedStatement.resultType = resultType;
mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
}
public MappedStatement build() {
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
return mappedStatement;
}
public String id() {
return mappedStatement.id;
}
public Builder resultMaps(List<ResultMap> resultMaps) {
mappedStatement.resultMaps = resultMaps;
return this;
}
}
public Configuration getConfiguration() {
return configuration;
}
public String getId() {
return id;
}
public SqlCommandType getSqlCommandType() {
return sqlCommandType;
}
public SqlSource getSqlSource() {
return sqlSource;
}
public Class<?> getResultType() {
return resultType;
}
public LanguageDriver getLang() {
return lang;
}
public List<ResultMap> getResultMaps() {
return resultMaps;
}
}
- 添加
List<ResultMap> resultMaps
结果映射列表
3.3.4 映射构建器助手
- MapperBuilderAssistant 构建器助手专门为创建 MappedStatement 映射语句类而服务的,在这个类中封装了入参和出参的映射、以及把这些配置信息写入到 Configuration 配置项中。
MapperBuilderAssistant.java
package com.lino.mybatis.builder;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* @description: 映射构建器助手,建造者
*/
public class MapperBuilderAssistant extends BaseBuilder {
private String currentNamespace;
private String resource;
public MapperBuilderAssistant(Configuration configuration, String resource) {
super(configuration);
this.resource = resource;
}
public String getCurrentNamespace() {
return currentNamespace;
}
public void setCurrentNamespace(String currentNamespace) {
this.currentNamespace = currentNamespace;
}
public String applyCurrentNamespace(String base, boolean isReference) {
if (base == null) {
return null;
}
if (isReference) {
if (base.contains(".")) {
return base;
}
}
return currentNamespace + "." + base;
}
/**
* 添加映射器语句
*/
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, SqlCommandType sqlCommandType,
Class<?> parameterType, String resultMap, Class<?> resultType,
LanguageDriver lang) {
// 给id加上namespace前缀:com.lino.mybatis.test.dao.IUserDao.queryUserInfoById
id = applyCurrentNamespace(id, false);
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);
// 结果映射, 给 MappedStatement#resultMaps
setStatementResultMap(resultMap, resultType, statementBuilder);
MappedStatement statement = statementBuilder.build();
// 映射语句信息,建造完存放到配置项中
configuration.addMappedStatement(statement);
return statement;
}
private void setStatementResultMap(String resultMap, Class<?> resultType, MappedStatement.Builder statementBuilder) {
// 因为暂时还没有在 Mapper XML 中配置 Map 返回结果,所以这里返回的是 null
resultMap = applyCurrentNamespace(resultMap, true);
List<ResultMap> resultMaps = new ArrayList<>();
if (resultMap != null) {
// TODO 暂无Map结果映射配置
}
/*
* 通常使用 resultType 即可满足大部分场景
* <select id="queryUserInfoById" resultType="com.lino.mybatis.test.po.User">
* 使用 resultType 的情况下,Mybatis 会自动创建一个 ResultMap,基于属性名称映射列到 JavaBean 的属性上。
*/
else if (resultType != null) {
ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
configuration, statementBuilder.id() + "-Inline", resultType, new ArrayList<>());
resultMaps.add(inlineResultMapBuilder.build());
}
statementBuilder.resultMaps(resultMaps);
}
}
- 在映射构建器助手中,提供了添加映射器语句的方法,在这个方法中更加标准的封装了入参和出参信息。
- 如果这些方内容全部堆砌到 XMLStatementBuilder 语句构建器的解析中,就会显得非常臃肿不易于维护了。
- 在
MapperBuilderAssistant#setStatementResultMap
方法中,其实它只是一个非常简单的结果映射建造的过程,无论是否为 ResultMap 都会进行这样的封装处理。并最终把创建的信息写入到 MappedStatement 映射语句类中。
3.3.5 语句构建器调用助手类
XMLStatementBuilder.java
package com.lino.mybatis.builder.xml;
import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;
import java.util.Locale;
/**
* @description: XML语言构建器
*/
public class XMLStatementBuilder extends BaseBuilder {
private MapperBuilderAssistant builderAssistant;
private Element element;
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, Element element) {
super(configuration);
this.builderAssistant = builderAssistant;
this.element = element;
}
/**
* 解析语句(select|insert|update|delete)
* <select
* id="selectPerson"
* parameterType="int"
* parameterMap="deprecated"
* resultType="hashmap"
* resultMap="personResultMap"
* flushCache="false"
* useCache="true"
* timeout="10000"
* fetchSize="256"
* statementType="PREPARED"
* resultSetType="FORWARD_ONLY">
* SELECT * FROM PERSON WHERE ID = #{id}
* </select>
*/
public void parseStatementNode() {
String id = element.attributeValue("id");
// 参数类型
String parameterType = element.attributeValue("parameterType");
Class<?> parameterTypeClass = resolveAlias(parameterType);
// 外部应用 resultMap
String resultMap = element.attributeValue("resultMap");
// 结果类型
String resultType = element.attributeValue("resultType");
Class<?> resultTypeClass = resolveAlias(resultType);
// 获取命令类型(select|insert|update|delete)
String nodeName = element.getName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 获取默认语言驱动器
Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
LanguageDriver langDriver = configuration.getLanguageRegistry().getDriver(langClass);
// 解析成SqlSource,DynamicSqlSource/RawSqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, element, parameterTypeClass);
// 调用助手类
builderAssistant.addMappedStatement(id, sqlSource, sqlCommandType, parameterTypeClass, resultMap, resultTypeClass, langDriver);
}
}
- 对于这部分的解析后的结果处理的职责内容,划分到了新增加的助手类中。
- 这种实现方式在 Mybatis 的源码中还是非常多的,大部分的内容处理,都会提供一个助手类进行操作。
3.3.6 映射构建器调用助手类
XMLMapperBuilder.java
package com.lino.mybatis.builder.xml;
import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
/**
* @description: XML映射构建器
*/
public class XMLMapperBuilder extends BaseBuilder {
private Element element;
private String resource;
/**
* 映射器构建助手
*/
private MapperBuilderAssistant builderAssistant;
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource) throws DocumentException {
this(new SAXReader().read(inputStream), configuration, resource);
}
public XMLMapperBuilder(Document document, Configuration configuration, String resource) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.element = document.getRootElement();
this.resource = resource;
}
/**
* 解析
*
* @throws Exception 异常
*/
public void parse() throws Exception {
// 如果当前资源没有加载过再加载,防止重复加载
if (!configuration.isResourceLoaded(resource)) {
configurationElement(element);
// 标记一下,已经加载过了
configuration.addLoadedResource(resource);
// 绑定映射器到namespace
configuration.addMapper(Resources.classForName(builderAssistant.getCurrentNamespace()));
}
}
/**
* 配置mapper元素
* <mapper namespace="org.mybatis.example.BlogMapper">
* <select id="selectBlog" parameterType="int" resultType="Blog">
* select * from Blog where id = #{id}
* </select>
* </mapper>
*
* @param element 元素
*/
private void configurationElement(Element element) {
// 1.配置namespace
String namespace = element.attributeValue("namespace");
if ("".equals(namespace)) {
throw new RuntimeException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 2.配置select|insert|update|delete
buildStatementFromContext(element.elements("select"));
}
/**
* 配置select|insert|update|delete
*
* @param list 元素列表
*/
private void buildStatementFromContext(List<Element> list) {
for (Element element : list) {
final XMLStatementBuilder statementBuilder = new XMLStatementBuilder(configuration, builderAssistant, element);
statementBuilder.parseStatementNode();
}
}
}
3.4 类型处理器
3.4.1 类型处理器接口
TypeHandler.java
package com.lino.mybatis.type;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @description: 类型处理器
*/
public interface TypeHandler<T> {
/**
* 设置参数
*
* @param ps 预处理语言
* @param i 次数
* @param parameter 参数对象
* @param jdbcType JDBC类型
* @throws SQLException SQL异常
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 获取结果
*
* @param rs 结果列表
* @param columnName 列名
* @return T 结果
* @throws SQLException
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
}
3.4.2 类型处理器的基类
BaseTypeHandler.java
package com.lino.mybatis.type;
import com.lino.mybatis.session.Configuration;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @description: 类型处理器的基类
*/
public abstract class BaseTypeHandler<T> implements TypeHandler<T> {
protected Configuration configuration;
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
// 定义抽象方法,由子类实现不同类型的属性设置
setNonNullParameter(ps, i, parameter, jdbcType);
}
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
return getNullableResult(rs, columnName);
}
/**
* 属性设置:抽象方法,由子类实现
*
* @param ps 预处理语言
* @param i 次数
* @param parameter 参数对象
* @param jdbcType JDBC类型
* @throws SQLException SQL异常
*/
protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 获取空结果
*
* @param rs 结果列表
* @param columnName 列名
* @return T 结果
* @throws SQLException
*/
protected abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
}
3.4.3 具体子类类型处理器
IntegerTypeHandler.java
package com.lino.mybatis.type;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @description: Integer类型处理器
*/
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
@Override
protected void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter);
}
@Override
protected Integer getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getInt(columnName);
}
}
LongTypeHandler.java
package com.lino.mybatis.type;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @description: Long类型处理器
*/
public class LongTypeHandler extends BaseTypeHandler<Long> {
@Override
protected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {
ps.setLong(i, parameter);
}
@Override
protected Long getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getLong(columnName);
}
}
StringTypeHandler.java
package com.lino.mybatis.type;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @description: String类型处理器
*/
public class StringTypeHandler extends BaseTypeHandler<String> {
@Override
protected void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
protected String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
}
3.5 添加分页记录限制配置
3.5.1 分页记录限制
RowBounds.java
package com.lino.mybatis.session;
/**
* @description: 分页记录限制
*/
public class RowBounds {
public static final int NO_ROW_OFFSET = 0;
public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
public static final RowBounds DEFAULT = new RowBounds();
/**
* 分页-开始条数
*/
private int offset;
/**
* 分页-限制条数
*/
private int limit;
/**
* 默认是一页Integer.MAX_VALUE条
*/
public RowBounds() {
this.offset = NO_ROW_OFFSET;
this.limit = NO_ROW_LIMIT;
}
public RowBounds(int offset, int limit) {
this.offset = offset;
this.limit = limit;
}
public int getOffset() {
return offset;
}
public int getLimit() {
return limit;
}
}
3.5.2 修改执行器接口
Executor.java
package com.lino.mybatis.executor;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import java.sql.SQLException;
import java.util.List;
/**
* @description: 执行器
*/
public interface Executor {
...
/**
* 查询
*
* @param ms 映射器语句
* @param parameter 参数
* @param rowBounds 分页记录限制
* @param resultHandler 结果处理器
* @param boundSql SQL对象
* @param <E> 返回的类型
* @return List<E>
*/
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql);
...
}
3.5.3 修改执行器抽象基类
BaseExecutor.java
package com.lino.mybatis.executor;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.List;
/**
* @description: 执行器抽象基类
*/
public abstract class BaseExecutor implements Executor {
...
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
if (closed) {
throw new RuntimeException("Executor was closed.");
}
return doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
}
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql);
...
}
3.5.4 修改简单执行器
SimpleExecutor.java
package com.lino.mybatis.executor;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* @description: 简单执行器
* @author: lingjian
* @createDate: 2022/11/8 13:42
*/
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
protected <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
Connection connection = transaction.getConnection();
Statement stmt = handler.prepare(connection);
handler.parameterize(stmt);
return handler.query(stmt, resultHandler);
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
}
3.5.5 修改语句处理器抽象基类
BaseStatementHandler.java
package com.lino.mybatis.executor.statement;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
/**
* @description: 语句处理器抽象基类
*/
public abstract class BaseStatementHandler implements StatementHandler {
protected final Configuration configuration;
protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final Object parameterObject;
protected final ResultSetHandler resultSetHandler;
protected final ParameterHandler parameterHandler;
protected final RowBounds rowBounds;
protected BoundSql boundSql;
public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.parameterObject = parameterObject;
this.rowBounds = rowBounds;
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, resultHandler, boundSql);
}
...
}
3.5.6 修改预处理语句处理器
PreparedStatementHandler.java
package com.lino.mybatis.executor.statement;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* @description: 预处理语句处理器(PREPARED)
*/
public class PreparedStatementHandler extends BaseStatementHandler {
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler resultSetHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameterObject, rowBounds, resultSetHandler, boundSql);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
return connection.prepareStatement(sql);
}
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
}
3.5.7 修改简单语句处理器
SimpleStatementHandler.java
package com.lino.mybatis.executor.statement;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* @description: 简单语句处理器(STATEMENT)
*/
public class SimpleStatementHandler extends BaseStatementHandler {
public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
return connection.createStatement();
}
@Override
public void parameterize(Statement statement) {
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleResultSets(statement);
}
}
3.5.8 修该配置项
Configuration.java
package com.lino.mybatis.session;
import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.datasource.pooled.PooledDataSourceFactory;
import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.SimpleExecutor;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.DefaultResultSetHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.executor.statement.PreparedStatementHandler;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.DefaultObjectFactory;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.reflection.wrapper.DefaultObjectWrapperFactory;
import com.lino.mybatis.reflection.wrapper.ObjectWrapperFactory;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.LanguageDriverRegistry;
import com.lino.mybatis.scripting.xmltags.XMLLanguageDriver;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @description: 配置项
*/
public class Configuration {
...
/**
* 创建语句处理器
*
* @param executor 执行器
* @param mappedStatement 映射器语句类
* @param parameter 参数
* @param rowBounds 分页记录限制
* @param resultHandler 结果处理器
* @param boundSql SQL语句
* @return StatementHandler 语句处理器
*/
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
return new PreparedStatementHandler(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
/**
* 创建结果集处理器
*
* @param executor 执行器
* @param mappedStatement 映射器语句类
* @param boundSql SQL语句
* @return ResultSetHandler 结果集处理器
*/
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
return new DefaultResultSetHandler(executor, mappedStatement, resultHandler, rowBounds, boundSql);
}
...
}
3.5.9 修改默认SqlSession实现类
DefaultSqlSession.java
package com.lino.mybatis.session.defaults;
import com.alibaba.fastjson.JSON;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* @description: 默认sqlSession实现类
*/
public class DefaultSqlSession implements SqlSession {
...
@Override
public <T> T selectOne(String statement, Object parameter) {
logger.info("执行查询 statement:{} parameter:{}", statement, JSON.toJSONString(parameter));
MappedStatement ms = configuration.getMappedStatement(statement);
List<T> list = executor.query(ms, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, ms.getSqlSource().getBoundSql(parameter));
return list.get(0);
}
...
}
3.6 结果集处理器
- 从 DefaultSqlSession 调用 Executor 语句执行器,一直到 PreparedStatementHandler 预处理语句处理,最后就是 DefaultResultSetHandler 结果信息的封装。
- 前面章节中对此处的封装处理,没有解耦的操作,只是简单的 JDBC 使用通过查询结果,反射处理返回信息就结束了。
- 所以这部分的内容处理需要被解耦。分为:对象的实例化、结果信息的封装、策略模式的处理、写入上下文返回等操作。
- 只有通过这样的解耦流程,才能更加方便的扩展流程不同节点中的各类需求。
- 这是一套结果封装的核心处理流程,包括创建处理器、封装数据和保存结果。
3.6.1 结果上下文接口
ResultContext.java
package com.lino.mybatis.session;
/**
* @description: 结果上下文
*/
public interface ResultContext {
/**
* 获取结果
*
* @return 结果对象
*/
Object getResultObject();
/**
* 获取记录数
*
* @return 记录数
*/
int getResultCount();
}
3.6.2 默认结果上下文实现类
DefaultResultContext.java
package com.lino.mybatis.executor.result;
import com.lino.mybatis.session.ResultContext;
/**
* @description: 默认结果上下文
*/
public class DefaultResultContext implements ResultContext {
private Object resultObject;
private int resultCount;
public DefaultResultContext() {
this.resultObject = null;
this.resultCount = 0;
}
@Override
public Object getResultObject() {
return resultObject;
}
@Override
public int getResultCount() {
return resultCount;
}
public void nextResultObject(Object resultObject) {
resultCount++;
this.resultObject = resultObject;
}
}
3.6.3 结果集处理器
ResultHandler.java
package com.lino.mybatis.session;
/**
* @description: 结果处理器
*/
public interface ResultHandler {
/**
* 处理结果
*/
void handleResult(ResultContext context);
}
3.6.4 默认结果集处理器
DefaultResultHandler.java
package com.lino.mybatis.executor.result;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.session.ResultContext;
import com.lino.mybatis.session.ResultHandler;
import java.util.ArrayList;
import java.util.List;
/**
* @description: 默认结果处理器
*/
public class DefaultResultHandler implements ResultHandler {
private final List<Object> list;
public DefaultResultHandler() {
this.list = new ArrayList<>();
}
@SuppressWarnings("unchecked")
public DefaultResultHandler(ObjectFactory objectFactory) {
this.list = objectFactory.create(List.class);
}
@Override
public void handleResult(ResultContext context) {
list.add(context.getResultObject());
}
public List<Object> getResultList() {
return list;
}
}
- 这里封装了一个非常简单的结果集对象, 默认情况下都会写入到这个对象的 list 集合中。
3.7 创建对象,属性填充
3.7.1 结果集包装器
ResultSetWrapper.java
package com.lino.mybatis.executor.resultset;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.JdbcType;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;
/**
* @description: 结果集包装器
*/
public class ResultSetWrapper {
private final ResultSet resultSet;
private final TypeHandlerRegistry typeHandlerRegistry;
private final List<String> columnNames = new ArrayList<>();
private final List<String> classNames = new ArrayList<>();
private final List<JdbcType> jdbcTypes = new ArrayList<>();
private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
private Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
private Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();
public ResultSetWrapper(ResultSet resultSet, Configuration configuration) throws SQLException {
super();
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.resultSet = resultSet;
final ResultSetMetaData metaData = resultSet.getMetaData();
final int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
columnNames.add(metaData.getColumnLabel(i));
jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
classNames.add(metaData.getColumnClassName(i));
}
}
public ResultSet getResultSet() {
return resultSet;
}
public List<String> getColumnNames() {
return this.columnNames;
}
public List<String> getClassNames() {
return Collections.unmodifiableList(classNames);
}
public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
TypeHandler<?> handler = null;
Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
if (columnHandlers == null) {
columnHandlers = new HashMap<>(16);
typeHandlerMap.put(columnName, columnHandlers);
} else {
handler = columnHandlers.get(propertyType);
}
if (handler == null) {
handler = typeHandlerRegistry.getTypeHandler(propertyType, null);
columnHandlers.put(propertyType, handler);
}
return handler;
}
private Class<?> resolveClass(String className) {
try {
return Resources.classForName(className);
} catch (ClassNotFoundException e) {
return null;
}
}
private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
List<String> mappedColumnNames = new ArrayList<>();
List<String> unmappedColumnNames = new ArrayList<>();
final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
for (String columnName : columnNames) {
final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
if (mappedColumns.contains(upperColumnName)) {
mappedColumnNames.add(upperColumnName);
} else {
unmappedColumnNames.add(columnName);
}
}
mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}
public List<String> getMappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
List<String> mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
if (mappedColumnNames == null) {
loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
}
return mappedColumnNames;
}
public List<String> getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
List<String> umMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
if (umMappedColumnNames == null) {
loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
umMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
}
return umMappedColumnNames;
}
private String getMapKey(ResultMap resultMap, String columnPrefix) {
return resultMap.getId() + ":" + columnPrefix;
}
private Set<String> prependPrefixes(Set<String> columnNames, String prefix) {
if (columnNames == null || columnNames.isEmpty() || prefix == null || prefix.length() == 0) {
return columnNames;
}
final Set<String> prefixed = new HashSet<>();
for (String columnName : columnNames) {
prefixed.add(prefix + columnName);
}
return prefixed;
}
}
3.7.2 默认Map结果处理器
- 在处理封装数据的过程中,包括根据 resultType 使用反射工具类
ObjectFactory#create
方法创建出 Bean 对象。这个过程会根据不同的类型进行创建。 - 调用链路:
handlerResultSet -> handlerRowValuesForSimpleResultMap -> getRowValue -> createResultObject
- 对象实例化完成后,就是根据 ResultSet 获取出对应的值填充到对象的属性中。
- 注意:这个结果的获取来自于
TypeHandler#getResult
接口新增的方法,由不同的类型处理器实现,通过这样的策略模式设计方式就可以巧妙的避免 if-else 的判断处理。
- 注意:这个结果的获取来自于
DefaultResultSetHandler.java
package com.lino.mybatis.executor.resultset;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.result.DefaultResultContext;
import com.lino.mybatis.executor.result.DefaultResultHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.mapping.ResultMapping;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;
import com.sun.xml.internal.ws.api.streaming.XMLStreamWriterFactory;
import java.lang.reflect.Method;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* @description: 默认Map结果处理器
*/
public class DefaultResultSetHandler implements ResultSetHandler {
private final Configuration configuration;
private final MappedStatement mappedStatement;
private final RowBounds rowBounds;
private final ResultHandler resultHandler;
private final BoundSql boundSql;
private final TypeHandlerRegistry typeHandlerRegistry;
private final ObjectFactory objectFactory;
public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ResultHandler resultHandler, RowBounds rowBounds, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.rowBounds = rowBounds;
this.boundSql = boundSql;
this.mappedStatement = mappedStatement;
this.resultHandler = resultHandler;
this.objectFactory = configuration.getObjectFactory();
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
}
@Override
@SuppressWarnings("unchecked")
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = new ResultSetWrapper(stmt.getResultSet(), configuration);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
while (rsw != null && resultMaps.size() > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
resultSetCount++;
}
return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
}
private ResultSetWrapper getNextResultSet(Statement stmt) throws SQLException {
// Making this method tolerant of bad JDBC drivers
try {
if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
// Crazy Standard JDBC way of determining if there are more results
if (!((!stmt.getMoreResults()) && (stmt.getUpdateCount() == -1))) {
ResultSet rs = stmt.getResultSet();
return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}
}
} catch (Exception ignore) {
// Intentionally ignored.
}
return null;
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
if (resultHandler == null) {
// 1.新创建结果处理器
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
// 2.封装数据
handleRowValuesForSimpleResultMap(rsw, resultMap, defaultResultHandler, rowBounds, null);
// 3.保存结果
multipleResults.add(defaultResultHandler.getResultList());
}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
DefaultResultContext resultContext = new DefaultResultContext();
while (resultContext.getResultCount() < rowBounds.getLimit() && rsw.getResultSet().next()) {
Object rowValue = getRowValue(rsw, resultMap);
callResultHandler(resultHandler, resultContext, rowValue);
}
}
private void callResultHandler(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue) {
resultContext.nextResultObject(rowValue);
resultHandler.handleResult(resultContext);
}
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
// 根据返回类型,实例化对象
Object resultObject = createResultObject(rsw, resultMap, null);
if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(resultObject);
applyAutomaticMappings(rsw, resultMap, metaObject, null);
}
return resultObject;
}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final List<Class<?>> constructorArgTypes = new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
return createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) throws SQLException {
final Class<?> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType);
if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
// 普通的Bean对象类型
return objectFactory.create(resultType);
}
throw new RuntimeException("Do not know how to create an instance of " + resultType);
}
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
for (String columnName : unmappedColumnNames) {
String propertyName = columnName;
if (columnPrefix != null && !columnPrefix.isEmpty()) {
// When columnPrefix is specified,ignore columns without the prefix.
if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
propertyName = columnName.substring(columnPrefix.length());
} else {
continue;
}
}
final String property = metaObject.findProperty(propertyName, false);
if (property != null && metaObject.hasSetter(property)) {
final Class<?> propertyType = metaObject.getSetterType(property);
if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
// 使用 TypeHandler 取得结果
final Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
if (value != null) {
foundValues = true;
}
if (value != null || !propertyType.isPrimitive()) {
// 通过反射工具类设置属性值
metaObject.setValue(property, value);
}
}
}
}
return foundValues;
}
}
createResultObject
,对于这样的普通对象,只需要使用反射工具类就可以实例化对象了,不过这个时候属性信息还没有填充。applyAutomaticMappings
,这个方法就是实现具体的属性填充。columnName
是属性名称,根据属性名称,按照反射工具类从对象中获取对应的 propertyType 属性类型,之后再根据类型获取到 TypeHandler 类型处理器。有了具体的类型处理器,在获取每一个类型处理器下的结果内容就更加方便了。- 获取属性值后,再使用 MetaObject 反射工具类设置属性值,一次循环设置完成以后,这样一个完整的结果信息 Bean 对象就可以返回了。
- 返回后写入到 DefaultResultContext#nextResultObject 上下文中。
四、测试:结果集处理器
ApiTest.java
@Test
public void test_queryUserInfoId() {
// 1.获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 2.测试验证: 基本参数
User user = userDao.queryUserInfoById(1L);
logger.info("测试结果:{}", JSON.toJSONString(user));
}
测试结果
11:28:12.555 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:id propertyType:class java.lang.Long
11:28:12.769 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IUserDao.queryUserInfoById parameter:1
11:28:13.592 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 1043208434.
11:28:13.592 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:1
11:28:13.633 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
- 通过 DefaultResultSetHandler 结果处理器的功能解耦和实现,已经可以正常查询和返回对应的对象信息。
五、总结:结果集处理器
- 整个功能实现,围绕流程的解耦进行处理,将对象的参数解析和结果封装都进行拆解,通过这样的方式来分配各个模块的单一职责,不让一个类的方法承担过多的交叉功能。