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

手写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 结果处理器的功能解耦和实现,已经可以正常查询和返回对应的对象信息。

五、总结:结果集处理器

  • 整个功能实现,围绕流程的解耦进行处理,将对象的参数解析和结果封装都进行拆解,通过这样的方式来分配各个模块的单一职责,不让一个类的方法承担过多的交叉功能。
http://www.lryc.cn/news/153732.html

相关文章:

  • 金融风控数据分析-信用评分卡建模(附数据集下载地址)
  • ceph对象三元素data、xattr、omap
  • 使用 BERT 进行文本分类 (03/3)
  • Leetcode Top 100 Liked Questions(序号236~347)
  • MySQL数据库学习【基础篇】
  • Kubernetes技术--k8s核心技术Service服务
  • OpenHarmony 应用 ArkUI 状态管理开发范例
  • 二、QTableWidget 类 clear() 和 clearContents() 的区别及程序崩溃原因分析
  • spring boot 项目中搭建 ElasticSearch 中间件 一 postman 操作 es
  • 设计模式—观察者模式(Observer)
  • 分类算法系列③:模型选择与调优 (Facebook签到位置预测)
  • PCL RANSAC分割提取多个空间圆
  • Java八股文学习笔记day01
  • vant的NavBar导航栏可以自定义背景图片吗
  • 深入浅出AXI协议(5)——数据读写结构读写响应结构
  • IntelliJ Idea开发Vue遇到的几个问题
  • sql查找最晚一天/日期最大的一条记录 两种方法
  • 详解python的
  • Modbus TCP通信笔记
  • CIM和websockt-实现实时消息通信:双人聊天和消息列表展示
  • useLayoutEffect和useEffect有什么作用?
  • django中配置使用websocket终极解决方案
  • 敦煌网、Jumia等跨境电商平台怎么测评(补单)留评?
  • uni-app之android离线打包
  • 【传输层】TCP -- 三次握手四次挥手 | 可靠性与提高性能策略
  • 前端将UTC时间格式转化为本地时间格式~~uniapp写法
  • 说说Kappa架构
  • 项目介绍:《Online ChatRoom》网页聊天室 — Spring Boot、MyBatis、MySQL和WebSocket的奇妙融合
  • Vue3 学习 组合式API setup语法糖 响应式 指令 DIFF(一)
  • 一文轻松入门DeepSort