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

五、总结:结果集处理器

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

相关文章:

手写Mybatis:第11章-流程解耦,封装结果集处理器

文章目录 一、目标&#xff1a;结果集处理器二、设计&#xff1a;结果集处理器三、实现&#xff1a;结果集处理器3.1 工程结构3.2 结果集处理器关系图3.3 出参参数处理3.3.1 结果映射Map3.3.2 结果映射封装3.3.3 修改映射器语句类3.3.4 映射构建器助手3.3.5 语句构建器调用助手…...

金融风控数据分析-信用评分卡建模(附数据集下载地址)

本文引用自&#xff1a; 金融风控&#xff1a;信用评分卡建模流程 - 知乎 (zhihu.com) 在原文的基础上加上了一部分自己的理解&#xff0c;转载在CSDN上作为保留记录。 本文涉及到的数据集可直接从天池上面下载&#xff1a; Give Me Some Credit给我一些荣誉_数据集-阿里云…...

ceph对象三元素data、xattr、omap

这里有一个ceph的原则&#xff0c;就是所有存储的不管是块设备、对象存储、文件存储最后都转化成了底层的对象object&#xff0c;这个object包含3个元素data&#xff0c;xattr&#xff0c;omap。data是保存对象的数据&#xff0c;xattr是保存对象的扩展属性&#xff0c;每个对象…...

使用 BERT 进行文本分类 (03/3)

一、说明 在使用BERT&#xff08;2&#xff09;进行文本分类时&#xff0c;我们讨论了什么是PyTorch以及如何预处理我们的数据&#xff0c;以便可以使用BERT模型对其进行分析。在这篇文章中&#xff0c;我将向您展示如何训练分类器并对其进行评估。 二、准备数据的又一个步骤 …...

Leetcode Top 100 Liked Questions(序号236~347)

236. Lowest Common Ancestor of a Binary Tree 题意&#xff1a;二叉树&#xff0c;求最近公共祖先&#xff0c;All Node.val are unique. 我的思路 首先把每个节点的深度得到&#xff0c;之后不停向上&#xff0c;直到val相同&#xff0c;存深度就用map存吧 但是它没有向…...

MySQL数据库学习【基础篇】

&#x1f4c3;基础篇 下方链接使用科学上网速度可能会更加快一点哦&#xff01; 请点击查看数据库MySQL笔记大全 通用语法及分类 DDL: 数据定义语言&#xff0c;用来定义数据库对象&#xff08;数据库、表、字段&#xff09;DML: 数据操作语言&#xff0c;用来对数据库表中的…...

Kubernetes技术--k8s核心技术Service服务

1.service概述 Service 是 Kubernetes 最核心概念,通过创建 Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。 2.service存在的意义 -1:防止pod失联(服务发现) 我们先说一下什么叫pod失联。 -2:...

OpenHarmony 应用 ArkUI 状态管理开发范例

本文转载自《#2023 盲盒码 # OpenHarmony 应用 ArkUI 状态管理开发范例》&#xff0c;作者&#xff1a;zhushangyuan_ 本文根据橘子购物应用&#xff0c;实现 ArkUI 中的状态管理。 在声明式 UI 编程框架中&#xff0c;UI 是程序状态的运行结果&#xff0c;用户构建了一个 UI …...

二、QTableWidget 类 clear() 和 clearContents() 的区别及程序崩溃原因分析

问题描述&#xff1a;区分 QTableWidget 类的 clear() 和 clearContents() 的用法&#xff0c;以及可能由于这两个方法使用不当导致程序崩溃的原因分析 Qt 官方文档对 QTableWidget 类的 clear() 方法描述如下&#xff1a; [slot] void QTableWidget::clear() Removes all ite…...

spring boot 项目中搭建 ElasticSearch 中间件 一 postman 操作 es

postman 操作 es 1. 简介2. 环境3. postman操作索引3.1 创建索引3.2 查看索引3.3 查看所有索引3.4 删除索引 4. postman操作文档4.1 添加文档4.2 查询文档4.3 查询全部文档4.4 更新文档4.5 局部更新文档4.6 删除文档4.7 条件查询文档14.8 条件查询文档24.9 条件查询文档 limit4…...

设计模式—观察者模式(Observer)

目录 思维导图 一、什么是观察者模式&#xff1f; 二、有什么优点吗&#xff1f; 三、有什么缺点吗&#xff1f; 四、什么时候使用观察者模式&#xff1f; 五、代码展示 ①、双向耦合的代码 ②、解耦实践一 ③、解耦实践二 ④、观察者模式 六、这个模式涉及到了哪些…...

分类算法系列③:模型选择与调优 (Facebook签到位置预测)

目录 模型选择与调优 1、介绍 模型选择&#xff08;Model Selection&#xff09;&#xff1a; 调优&#xff08;Hyperparameter Tuning&#xff09;&#xff1a; 本章重点 2、交叉验证 介绍 为什么需要交叉验证 数据处理 3、⭐超参数搜索-网格搜索(Grid Search) 介绍…...

PCL RANSAC分割提取多个空间圆

目录 一、概述二、代码实现三、结果展示1、原始数据2、提取结果四、测试数据本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、概述 使用PCL分割提取多个空间圆,其核心原理仍然是RANSAC拟合空间圆,这里只是做简单修改…...

Java八股文学习笔记day01

01.和equals区别 对于字符串变量来说&#xff0c;使用""和"equals"比较字符串时&#xff0c;其比较方法不同。""比较两个变量本身的值&#xff0c;即两个对象在内存中的首地址&#xff0c;"equals"比较字符串包含内容是否相同。 对于非…...

vant的NavBar导航栏可以自定义背景图片吗

可以的&#xff0c;Vant的NavBar导航栏提供了一个background-image属性&#xff0c;可以设置自定义背景图片。例 如&#xff1a; <van-nav-bar title"标题" left-text"返回" left-arrow background-image"url(https://example.com/image.jpg)&qu…...

深入浅出AXI协议(5)——数据读写结构读写响应结构

目录 一、前言 二、写选通&#xff08;Write strobes&#xff09; 三、窄传输&#xff08;Narrow transfers&#xff09; 1、示例1 2、示例2 四、字节不变性&#xff08;Byte invariance&#xff09; 五、未对齐的传输&#xff08;Unaligned transfers&#xff09; 六…...

IntelliJ Idea开发Vue遇到的几个问题

IntelliJ Idea开发Vue遇到的几个问题 确保 idea已安装插件【Vue.js】 问题1&#xff1a;ts方法错误 或 提示导入 import xxx.vue标红 解决办法&#xff1a;在 env.d.ts中添加以下代码(若无此文件&#xff0c;重新创建)&#xff1a; /* eslint-disable */ declare module *.…...

sql查找最晚一天/日期最大的一条记录 两种方法

例&#xff1a;查找最晚入职员工的所有信息 建表&#xff1a; CREATE TABLE employees ( emp_no int(11) NOT NULL, birth_date date NOT NULL, first_name varchar(14) NOT NULL, last_name varchar(16) NOT NULL, gender char(1) NOT NULL, hire_date date NOT NULL, PRIMA…...

详解python的

详解& 在Python中&#xff0c;使用&符号可以求取两种数据类型的交集&#xff1a; 集合&#xff08;Set&#xff09;&#xff1a;你可以使用&来计算两个集合的交集。例如&#xff1a; set1 {1, 2, 3, 4} set2 {3, 4, 5, 6} common_elements set1 & set2 pri…...

Modbus TCP通信笔记

目录 1 Modbus TCP 数据协议1.1 数据格式1.2 报文头(MBAP头)1.3 功能码1.4 Modbus 地址映射到 CPU 地址 2 Modbus TCP 通讯数据示例2.1 功能码01 读离散输出线圈2.2 功能码02 读离散输入线圈2.3 功能码03 读保持寄存器2.4 功能码04 读输入寄存器2.5 功能码05 写单个离散输出寄存…...

CIM和websockt-实现实时消息通信:双人聊天和消息列表展示

欢迎大佬的来访&#xff0c;给大佬奉茶 一、文章背景 有一个业务需求是&#xff1a;实现一个聊天室&#xff0c;我和对方可以聊天&#xff1b;以及有一个消息列表展示我和对方&#xff08;多个人&#xff09;的聊天信息和及时接收到对方发来的消息并展示在列表上。 项目框架概…...

useLayoutEffect和useEffect有什么作用?

useEffect 和 useLayoutEffect 都是 React 中的钩子函数,用于在组件渲染过程中执行副作用操作。它们的主要区别在于执行时机。 useEffect: useEffect 是异步执行的,它在浏览器渲染完成之后才执行。这意味着它不会阻塞浏览器的渲染过程,因此适合用于处理副作用,如数据获取、…...

django中配置使用websocket终极解决方案

django ASGI/Channels 启动和 ASGI/daphne的区别 Django ASGI/Channels 是 Django 框架的一个扩展&#xff0c;它提供了异步服务器网关接口&#xff08;ASGI&#xff09;协议的支持&#xff0c;以便处理实时应用程序的并发连接。ASGI 是一个用于构建异步 Web 服务器和应用程序…...

敦煌网、Jumia等跨境电商平台怎么测评(补单)留评?

评论的重要性是众所周知的&#xff0c;对于想要做卖家运营的人来说&#xff0c;它直接影响着产品的销量和排名 那么如何通过自养号测评来提升销量和排名呢&#xff1f; 我相信大家对这个问题已经有了一定的了解&#xff0c;拥有大量自养号可以通过这些号来通过关键词搜索、浏…...

uni-app之android离线打包

一 AndroidStudio创建项目 1.1&#xff0c;上一节演示了uni-app云打包&#xff0c;下面演示怎样androidStudio离线打包。在AndroidStudio里面新建空项目 1.2&#xff0c;下载uni-app离线SDK&#xff0c;离线SDK主要用于App本地离线打包及扩展原生能力&#xff0c;SDK下载链接h…...

【传输层】TCP -- 三次握手四次挥手 | 可靠性与提高性能策略

超时重传机制连接管理机制三次握手四次挥手滑动窗口拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常情况TCP小结基于TCP应用层协议理解 listen 的第二个参数 超时重传机制 主机A发送数据给B之后&#xff0c;可能因为网络拥堵等原因&#xff0c;数据无法到达主机B&#xff1…...

前端将UTC时间格式转化为本地时间格式~~uniapp写法

UTC时间格式是什么 首先我们先简单的了解一下&#xff1a;UTC时间&#xff08;协调世界时&#xff0c;Coordinated Universal Time&#xff09;使用24小时制&#xff0c;以小时、分钟、秒和毫秒来表示时间 HH:mm:ss.SSSHH 表示小时&#xff0c;取值范围为00到23。mm 表示分钟…...

说说Kappa架构

分析&回答 对于实时数仓而言&#xff0c;Lmabda架构有很明显的不足&#xff0c;首先同时维护两套系统&#xff0c;资源占用率高&#xff0c;其次这两套系统的数据处理逻辑相同&#xff0c;代码重复开发。 能否有一种架构&#xff0c;只需要维护一套系统&#xff0c;就可以…...

项目介绍:《Online ChatRoom》网页聊天室 — Spring Boot、MyBatis、MySQL和WebSocket的奇妙融合

在当今数字化社会&#xff0c;即时通讯已成为人们生活中不可或缺的一部分。为了满足这一需求&#xff0c;我开发了一个名为"WeTalk"的聊天室项目&#xff0c;该项目基于Spring Boot、MyBatis、MySQL和WebSocket技术&#xff0c;为用户提供了一个实时交流的平台。在本…...

Vue3 学习 组合式API setup语法糖 响应式 指令 DIFF(一)

文章目录 前言一、Composition Api二、setup语法糖三、响应式refreactive 四、其他一些关键点v-prev-oncev-memov-cloak 五、虚拟Dom五、diff算法 前言 本文用于记录学习Vue3的过程 一、Composition Api 我觉得首先VUE3最大的改变就是对于代码书写的改变&#xff0c;从原来选择…...

广东平台网站建设哪家好/使用网站模板快速建站

一、常见版本管理系统 1、SVN 集中式的版本控制系统&#xff0c;只有一个中央数据仓库&#xff0c;如果中央数据仓库挂了或者不能访问&#xff0c;所有的使用者无法使用svn&#xff0c;无法进行提交或者备份文件 2、Git 分布式的版本控制系统&#xff0c;在每个使用者电脑上就有…...

深圳网络营销网站/查询网站备案信息

一、单项选择题 1.&#xff08; &#xff09;开创了我国分国记事的国别体的史书编写体例。 A、《天论》 B、《国语》 C、《战国策》 D、《史记》 2&#xff0e;《春秋》是孔子根据&#xff08; &#xff09;国史料修订而成的。 A、楚国 B、鲁国 C、秦国 D、齐国 3&#xff0e;春…...

美工设计网站推荐/seo工资多少

原文地址 https://www.cnblogs.com/zadomn0920/p/6196962.html 项目的左侧面板 项目设置->Project Project Settings -> Modules Sources面板 Paths面板 dependencies面板 Project Settings - > Libraries Project Settings - > Facets Project Settings -> a…...

网站建设中 怎么办/百度学术官网

1.使用TensorFlow并行化训练神经网络 from IPython.display import Image %matplotlib inline1.1TensorFlow框架与模型训练性能 TensorFlow可以显著加快机器学习任务流程&#xff0c;了解其原理之前&#xff0c;不妨先明确在硬件设备上执行复杂的计算所遭遇的一些性能挑战。 …...

敬请期待的近义词/seo技术教学视频

Java接口测试之日志管理之AOP统一日志 https://blog.csdn.net/zuozewei/article/details/85375106 自定义Api接口访问系统日志记录 https://blog.csdn.net/m0_37125796/article/details/91561223 log4j的MDC配合Spring-mvc进行日志追踪 本文链接&#xff1a;https://blog.…...

wordpress简书主题安装/全国疫情最新消息

公众号关注 “GitHubDaily”设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01;转自大数据文摘&#xff0c;作者曹培信、池俊辉盼望着&#xff0c;盼望着&#xff0c;春节的脚步近了&#xff0c;然而&#xff0c;每年到这个时候&#xff0c;最难的&#xff0c;莫过于一张…...