手写Mybatis:第11章-流程解耦,封装结果集处理器
文章目录
- 一、目标:结果集处理器
- 二、设计:结果集处理器
- 三、实现:结果集处理器
- 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.queryUserInfoByIdid = applyCurrentNamespace(id, false);MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);// 结果映射, 给 MappedStatement#resultMapssetStatementResultMap(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 返回结果,所以这里返回的是 nullresultMap = 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);// 外部应用 resultMapString 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/RawSqlSourceSqlSource 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);// 绑定映射器到namespaceconfiguration.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.配置namespaceString namespace = element.attributeValue("namespace");if ("".equals(namespace)) {throw new RuntimeException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);// 2.配置select|insert|update|deletebuildStatementFromContext(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;}@Overridepublic void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {// 定义抽象方法,由子类实现不同类型的属性设置setNonNullParameter(ps, i, parameter, jdbcType);}@Overridepublic 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> {@Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException {ps.setInt(i, parameter);}@Overrideprotected 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> {@Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {ps.setLong(i, parameter);}@Overrideprotected 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> {@Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, parameter);}@Overrideprotected 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 {...@Overridepublic <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);}@Overrideprotected <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);}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();return connection.prepareStatement(sql);}@Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}@Overridepublic <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);}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {return connection.createStatement();}@Overridepublic void parameterize(Statement statement) {}@Overridepublic <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 {...@Overridepublic <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;}@Overridepublic Object getResultObject() {return resultObject;}@Overridepublic 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);}@Overridepublic 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 driverstry {if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {// Crazy Standard JDBC way of determining if there are more resultsif (!((!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 结果处理器的功能解耦和实现,已经可以正常查询和返回对应的对象信息。
五、总结:结果集处理器
- 整个功能实现,围绕流程的解耦进行处理,将对象的参数解析和结果封装都进行拆解,通过这样的方式来分配各个模块的单一职责,不让一个类的方法承担过多的交叉功能。