Mybatis源码学习第六天(核心流程分析)之Executor分析(补充)
补充上一章没有讲解的三个Executor执行器;
还是贴一下之前的代码吧;我发现其实有些分析注释还是写在代码里面比较好,方便大家理解,之前是我的疏忽,不好意思
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 第一个执行器 StatementHandler 创建 其实StatementHandler用的也是模板模式
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// StatementHandler对象创建stmt,ParameterHandler对参数进行处理
stmt = prepareStatement(handler, ms.getStatementLog());
// 通过StatementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
StatementHandler继承体系 BaseStatementHandler构建了骨架,下面的三个子类对他的特定方法做了实现;
还是说一下吧;
StatementHandler完成了Mybatis最核心的工作,也是Executor实现的基础,功能包括:创建Statement对象,为Sql语句绑定参数,执行增删改查等Sql语句,将结果映射集进行转化;
BaseStatementHandler:所有子类的抽象父类,定义了初始化Statement的操作顺序,由子类实现具体的实例化不同的statement(模板模式);
RoutingStatementHandler:Executor组件真正实例化的子类使用静态代理模式,根据上下文决定创建那个具体实体类;
SimpleStatementHandler:使用Statement对象访问数据库,无需参数化;
PrepareStatementHandler:使用预编译PrepareStatement对象访问数据库;
CallableStatementHandler:调用存储过程;
BaseStatementHandler定义的骨架
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 这个instantiateStatement这个方法就是交给子类去实现的 simple prepare callbale
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
返回之前的SimpleExecutor,从这行代码进去看看
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 在这里突然有点发懵,之前说了那么多,咋到这变了? 别着急进去看看
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 进来后发现 是使用的静态代理模式 根据上下文MappedStatement动态决定创建那个具体的子类 赋值给包装的 StatementHandler
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
} }
private final StatementHandler delegate;
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 上次说到这里还重点说了一下 日志模块的优雅嵌入
Connection connection = getConnection(statementLog);
// 开始构建stmt 这个方法就进入到了上面的BaseStatementHandler的骨架中 通过不同的StatementHandler,利用connection创建(prepare)statement
stmt = handler.prepare(connection, transaction.getTimeout());
// 使用ParameterHandler进行占位符参数处理
handler.parameterize(stmt);
return stmt;
}
在SimpleStatementHandler中参数是没有必要处理的,因为statement中是没有 ? 占位符的,所以他 虽然实现了这个方法 但是却是空的
@Override
public void parameterize(Statement statement) throws SQLException {
// N/A
}
在PreppareStatementHandler中做了一层封装,也就是调用ParameterHandler进行处理,
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
看一下参数处理器的数据结构
1 private final TypeHandlerRegistry typeHandlerRegistry; // 类型处理器注册中心
private final MappedStatement mappedStatement; // 对应的Sql节点的信息
private final Object parameterObject; // 用户传入的参数
private final BoundSql boundSql; // Sql语句的信息,包含?占位符和参数名称
private final Configuration configuration;
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 从boundSql中获取Sql语句的占位符和对应参数的信息
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) { //对于存储过程中的参数不处理
Object value;//绑定的实参
String propertyName = parameterMapping.getProperty(); // 参数的名字
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params 获取对应的实参值
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();//从parameterMapping中获取TypeHandler对象
JdbcType jdbcType = parameterMapping.getJdbcType();//获取参数对应的JDBCType
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 为Statement中的占位符绑定参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 获取Sql
String sql = boundSql.getSql();
// 执行
statement.execute(sql);
// ResultSetHandler处理结果
return resultSetHandler.<E>handleResultSets(statement);
}
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// 用于保存结果集对象
final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);//可能返回多个结果集,先取第一个 List<ResultMap> resultMaps = mappedStatement.getResultMaps();//获取对应的ResultMap 其实就是获取映射规则
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);//验证都不能为空,否则抛异常
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);//获取当前结果集对应的ResultMap
handleResultSet(rsw, resultMap, multipleResults, null);//根据映射规则对结果集进行转换,转化成目标对象放入multipleResultSet中
rsw = getNextResultSet(stmt);//获取下一个结果集
cleanUpAfterHandlingResultSet();//清空 nestedResultObjects对象
resultSetCount++;
}
// 获取多个结果集,多结果集一般出现在存储过程中,返回多个ResultSet
// mappedStatement.resultSets属性列出多了结果集的名称,用逗号分隔
// 多结果集的处理不是重点,暂时不分析
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
} return collapseSingleResultList(multipleResults);
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {//处理多结果集的嵌套映射
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) { //如果resultHandler为空 实例化一个默认的
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);//对resultSet进行映射,映射结果暂存在resultHandler中
multipleResults.add(defaultResultHandler.getResultList());//将暂存在resultHandler中的映射结果填充到multipleResults
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {//处理有嵌套ResultMap的情况
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {//处理没有嵌套的情况
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();//创建结果上下文,所谓的上下文就是专门在循环中缓存结果对象的
skipRows(rsw.getResultSet(), rowBounds);// 根据分页信息 定位到指定的记录
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {//判断是够需要映射后续结果,实际还是分页处理,避免超过limit
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);//进一步完善ResultMap信息,主要是处理鉴别器的信息
Object rowValue = getRowValue(rsw, discriminatedResultMap);//读取ResultSet中 的一行记录并进行映射,转化并返回目标对象
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());//保存映射结果对象
}
}
在这里说一下为什么不用Mybatis的rowBounds进行分页,因为他是把数据全部加载过来后,通过移动游标进行逻辑分页的效率差,数据量大的时候性能慢;分页就是为了网络传输性能快,数据量小;
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
rs.absolute(rowBounds.getOffset());
}
} else {// 如果不能准确定位 就需要一下一下的调用next
for (int i = 0; i < rowBounds.getOffset(); i++) {
rs.next();
}
}
}
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 根据ResultMap的type属性实例化目标对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 对目标对象封装得到metaObject,为后续的赋值操作做准备
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;//取得是否采用构造参数初始化属性值
if (shouldApplyAutomaticMappings(resultMap, false)) {//是否使用自动映射
// 一般情况下 autoMappingBehavior的默认值为PARTIAL,对为明确指定映射规则的字段进行自动映射
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
//映射ResultMap中明确指定需要映射的列
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
// 如果没有一个映射成功的属性,则根据<returnInstanceForEmpty>的配置返回null或者结果对象
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
作者:彼岸舞
时间:2020\03\23
内容关于:Mybatis
本文部分来源于网络,只做技术分享,一概不负任何责任
Mybatis源码学习第六天(核心流程分析)之Executor分析(补充)的更多相关文章
- Mybatis源码学习第六天(核心流程分析)之Executor分析
今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...
- mybatis源码学习:一级缓存和二级缓存分析
目录 零.一级缓存和二级缓存的流程 一级缓存总结 二级缓存总结 一.缓存接口Cache及其实现类 二.cache标签解析源码 三.CacheKey缓存项的key 四.二级缓存TransactionCa ...
- mybatis源码学习:插件定义+执行流程责任链
目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...
- mybatis源码学习:基于动态代理实现查询全过程
前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...
- mybatis源码学习(一) 原生mybatis源码学习
最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybati ...
- Mybatis源码学习第八天(总结)
源码学习到这里就要结束了; 来总结一下吧 Mybatis的总体架构 这次源码学习我们,学习了重点的模块,在这里我想说一句,源码的学习不是要所有的都学,一行一行的去学,这是错误的,我们只需要学习核心,专 ...
- Mybatis源码学习之整体架构(一)
简述 关于ORM的定义,我们引用了一下百度百科给出的定义,总体来说ORM就是提供给开发人员API,方便操作关系型数据库的,封装了对数据库操作的过程,同时提供对象与数据之间的映射功能,解放了开发人员对访 ...
- redis源码学习之工作流程初探
目录 背景 环境准备 下载redis源码 下载Visual Studio Visual Studio打开redis源码 启动过程分析 调用关系图 事件循环分析 工作模型 代码分析 动画演示 网络模块 ...
- Spring mybatis源码学习指引目录
前言: 分析了很多方面的mybatis的源码以及与spring结合的源码,但是难免出现错综的现象,为了使源码陶冶更为有序化.清晰化,特作此随笔归纳下分析过的内容.博主也为mybatis官方提供过pul ...
随机推荐
- 9、Java 常用类 Math,Number子类,String,Character
本小节主要介绍一些如何去使用Java提供的类如何去使用?如何在实战中使用?从来没有用过的如何去学习? 分享一下发哥的学习方法? 1.针对性的学习 在理解自己的需求或者要做某一块的内容后,有针对性,选择 ...
- Jenkins(Extended E-mail Notification)邮箱配置正确但是并没有发送邮件
废话 近期在把之前的接口自动化demo与jenkins集成,昨天发现了邮件配置正确但是没有发送邮件的问题,通过勾选系统设置 - >Extended E-mail Notification -&g ...
- ECS7天实践进阶训练营Day2:基于阿里云ECS部署MediaWiki
一.概述 MediaWiki是全球最著名的开源Wiki程序,运行于PHP+MySQL环境,MediaWiki从2002年被作为维基百科的系统软件,并由大量其他应用实例(例如萌娘百科),因此MediaW ...
- JDK 1.8 中文 API CHM
链接: https://pan.baidu.com/s/1AiJn6RM1KoEL1n_96qoQhQ 提取码: n2ya
- Java的注解浅析
人的一生就像一篇文章,只有经过多次精心修改,才能不断完善 Java注解概念理解: Java注解又称为Java标注,是JDK5引入的一中注释机制,Java中大家熟悉的五种注解分别是:@Override, ...
- three.js UV映射简述
今天郭先生来说一说uv映射,什么是uv映射?uv映射就是将二维的贴图映射到对象的一个面(或者多个面)上.说到这个问题,我们就不得不了解一下Geometry的点.面和uv的结构.我们以BoxGeomet ...
- 《Java从入门到失业》第二章:Java环境(二):JDK、JRE、JVM
2.2JDK.JRE.JVM 在JDK的安装目录中,我们发现有一个目录jre(其实如果是下一步下一步安装的,在和JDK安装目录同级目录下,还会有一个jre目录).初学Java的同学,有时候搞不清楚这3 ...
- Android 本地缓存Acache的简单使用
设置缓存数据: ACache mCache = ACache.get(this); mCache.put("key1", "value"); //保存6秒,如果 ...
- idea提升效率的插件
这篇文章用于记录idea插件.多分类记录确实可以提升效率. 1. FindBugs 虽说Idea本身提供的代码检查工具已经很强大了,但Idea提供的更多是规范性的检查,如果需要深入地检查异常,可以使用 ...
- META.表
META.表