目录

  • 概述
    • 职责
    • 类图
  • 源码
    • StatementHandler对象创建
  • 总结

概述

职责

首先了解一下statementHandler职责:主要负责处理MyBatis与JDBC之间Statement的交互,通俗而言就是负责操作Statement对象与数据库之间的交互。其执行过程中主要依赖ParameterHandler和ResultSetHandler进行参数绑定和结果实体类绑定。

类图

  • BaseStatementHandler:StatementHandler接口的抽象实现类,主要用于简化StatementHandler接口的实现难度,适配
    器设计模式的体现,主要有三个实现类:
  1. SimpleStatementHandler:管理Statement对象并向数据库推送不需要预编译的SQL语句;
  2. PreparedStatementHandler:管理Statement对象并向数据库推送需要预编译的SQL语句;
  3. CallableStatementHandler:管理Statement对象并调用数据库中的存储过程;
  • RoutingStatementHandler:StatementHandler接口的另一个实现类,并没有对Statement对象其实际执行作用,只是根据StatementType来创建一个代理,代理的主要对象即对应BaseStatementHandler的三种实现类。
    从源码层面理解RoutingStatementHandler:
 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {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());}}

源码

下面主要以查询为例,debugger源码层面分析。SQL执行开始皆从SqlSession开始。

  • DefaultSqlSession
    该类中针对增删改查存在多个重载方法,以selectList为例;
@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms = configuration.getMappedStatement(statement);//调用Executor中的queryreturn executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
  • BaseExecutor
 /*** 查询方法,专门提供select执行的方法*/@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {//获取查询SQLBoundSql boundSql = ms.getBoundSql(parameter);//创建缓存的key,即作为HashMap中的keyCacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);//执行查询return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}

此段代码块中涉及到BoundSQL对象,此处简单提一下相关概念,后期有时间专门总结一下BoundSQl.
BoundSQl对象主要是用于存储SQL语句,以及对应的参数相关对象。
继续调用BaseExcutor中的重载query方法:

 /*** 执行查询逻辑,* 首先从缓存中获取数据,缓存中有数据则进行处理存储过程;* 如果缓存中没有数据,则交互数据库查询数据,则将查询结果添加到缓存中*/@SuppressWarnings("unchecked")@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}//如果不是嵌套查询,且动态查询语句中flushCache = true时即<select id= "xx" flushCache = true>才会清空缓存if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {//嵌套查询层数+1queryStack++;//首先从一级缓存中进行查询:根据key获取对象list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {//从缓存中获取对象,则进行处理存储过程handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {//否则即缓存中没有对应的数据,则交互数据库从数据库中查询数据list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;}

继续跟进queryFromDatabase方法,该方法主要从数据库中查询数据

/*** 交互数据库从数据库中查询数据,再把查询结果添加到缓存中*/private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;//向缓存中添加占位符,此时缓存中没有真正所需要的查询数据localCache.putObject(key, EXECUTION_PLACEHOLDER);try {//查询数据库,由其子类实现,获取对应的查询数据list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {//删除占位符localCache.removeObject(key);}//再将从数据库查询的结果添加到一级缓存中localCache.putObject(key, list);//处理存储过程if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}

继续跟进doQuery方法,发现BaseExecutor抽象类中该方法并没有实体,仅提供一个钩子方法,而是交给其子类实现,这里体现了模板设计模式。

 /**交互数据库,查询数据*/protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException;

StatementHandler对象创建

  • SimpleExcutor
    从BaseExcutor类中跟进其子类SimpleExecutor看doQuery方法如何实现?
 @Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {// 获取环境配置Configuration configuration = ms.getConfiguration();//Configuration中获取StatementHandlerStatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}

到目前为止,我们发现了StatementHandler对象的来源自Configuration中newStatementHandler方法创建;
惊喜若现,继续跟进去;

  • configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {//创建StatementHandlerStatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}

最终,我们可以明确地发现StatementHandler对象是由其子类RoutingStatementHandler创建的,那么它创建的具体逻辑又是如何的呢?真相即将浮出水面,我们跟进RoutingStatementHandler的构造函数;

  • RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {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());}}

看到一段很熟悉的代码,我们在文章开头已经将其展现出来了。
根据statementType的类型来判断是哪一种StatementHandler的实现,并且RoutingStatementHandler维护了一个delegate对象,通过delegate对象来实现对实际Handler对象的调用。这里涉及到了一个对象MappedStatement。

  • SimpleExecutor
    众所周知,Excutor是主要负责执行对数据库的操作主要执行者,经历上面分析StatementHandler对象的创建过程,下面继续回归到SimpleExectuor中;
    获取到StatementHandler之后,首先进入prepareStatement方法,该方法就是为了获取Statement对象,并设置Statement对象中的参数:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return stmt;}
  • BaseStatementHandler
    prepare方法负责生成Statement实例对象
@Overridepublic Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());Statement statement = null;try {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);}}
  • prepareStatementHandler
    parameterize方法用于处理Statement实例对应的参数。此处我们跟进下去,便可以了解ParameterHandler是如何解析参数的过程。
@Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}
  • ParameterHandler
    此接口只有一个默认实现类DefaultParameterHandler,跟进setParameters方法
@Overridepublic void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());// parameterMappings 就是对 #{} 或者 ${} 里面参数的封装List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null) {// 如果是参数化的SQL,便需要循环取出并设置参数的值for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);// 如果参数类型不是 OUT ,这个类型与 CallableStatementHandler 有关// 因为存储过程不存在输出参数,所以参数不是输出参数的时候,就需要设置。if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;// 得到#{}  中的属性名String propertyName = parameterMapping.getProperty();// 如果 propertyName 是 Map 中的keyif (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params// 通过key 来得到 additionalParameter 中的value值value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {// 如果不是 additionalParameters 中的key,而且传入参数是 null, 则value 就是nullvalue = null;}// 如果 typeHandlerRegistry 中已经注册了这个参数的 Class对象,即它是Primitive 或者是String 的话else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {// 否则就是 MapMetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}// 在通过SqlSource 的parse 方法得到parameterMappings 的具体实现中,我们会得到parameterMappings的typeHandlerTypeHandler typeHandler = parameterMapping.getTypeHandler();// 获取typeHandler 的jdbc typeJdbcType jdbcType = parameterMapping.getJdbcType();if (value == null && jdbcType == null) {jdbcType = configuration.getJdbcTypeForNull();}try {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);}}}}}
  • SampleExecutor
    执行完成参数的解析,继续回归到SampleExecutor#doQuery()方法的流程中
 @Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {// 获取环境配置Configuration configuration = ms.getConfiguration();//Configuration中获取StatementHandlerStatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}

后面再次调用query方法,即SampleStatementHandler,PrepareStatementHandler,CallableStatementHandler中方法执行结果实体类绑定,这个具体过程解析,后面计划专门有文章总结。

总结

经历上面的跟踪源码,我们可以了解到StatementHandler对象具体的创建过程,以及参数和结果绑定的流程。

新手跟踪源码,若存在错误或者不足之处,希望大佬及时指正!最后,希望大家多多支持,转发,点赞,关注,谢谢。

Mybatis源码笔记之浅析StatementHandler相关推荐

  1. Mybatis源码笔记之浅析ParameterHandler

    目录 概念 职责 类图 源码 ParameterHandler对象创建 ParameterHandler解析参数 总结 概念 职责 ParameterHandler是用来设置参数规则的.Stateme ...

  2. 面试大厂被MyBatis问到“哑口无言”?这份MyBatis源码笔记助你吊打面试官!

    写在前面 随着手机.平板电脑等移动终端的广泛应用,移动互联网时代已经到来.在这个时代里,构建一个高效的平台并提供服务是移动互联网的基础,在众多的网站服务中,使用Java构建网站的不在少数,移动互联网的 ...

  3. mybatis源码阅读(六) ---StatementHandler了解一下

    转载自  mybatis源码阅读(六) ---StatementHandler了解一下 StatementHandler类结构图与接口设计 BaseStatementHandler:一个抽象类,只是实 ...

  4. mybatis源码深度解析_30天消化MyBatis源码解析笔记,吊打面试官,offer接到手软

    MyBatis 是一个优秀的 Java 持久化框架,SSM 框架组合(Spring + SpringMVC + Mybatis),依赖 MyBatis 搭建的项目更是数不胜数,在互联网公司的使用中,占 ...

  5. Mybatis源码学习笔记之Mybatis二级缓存

    简介   Mybatis一级缓存是会话级的缓存,而二级缓存则是应用级别的缓存,默认关闭,二级缓存使用不慎可能会导致脏读. 开启方式(SpringBoot+Mybatis)   application. ...

  6. MyBatis源码学习笔记(从设计模式看源码)

    文章目录 1.源码分析概述 ①.Mybatis架构分析 ②.门面模式 ③.设计模式的原则 2.日志模块分析 ①.适配器模型 ②.动态代理 ③.日志模块分析 3.数据源模块分析 ①.工厂模式 ②.数据源 ...

  7. Mybatis源码解读-设计模式总结

    虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式. Mybatis至少 ...

  8. mybatis源码-plugin源码

    在使用mybatis的时候,我们可以自己指定plugin,在sql执行过程中,增加一些额外的逻辑处理,这篇笔记主要记录plugin的原理 使用 1.声明自定义的plugin @Intercepts(@ ...

  9. 一周学完MyBatis源码,万字总结

    点击下方"IT牧场",选择"设为星标" 之前,我给大家分享给很多MyBatis源码分析的一系列文章.今天,就自己的感受来做一个整体的总结. 众所周知,MyBat ...

最新文章

  1. “自由主义教皇” 、​Linux 之父的封神之路
  2. rpm如何卸载mysql_Linux下卸载和安装MySQL[rpm包]
  3. Django之web框架的本质
  4. Django 【认证系统】auth
  5. MAC查找JDK的路径
  6. C语言 #include <> 与 #include “” 区别 - C语言零基础入门教程
  7. ansible的lookup
  8. 使用awk进行文本处理
  9. java多线程通信生产者和消费者简单例子
  10. 抖音python上的代码_抖音代码舞「图文推荐」,python实例代码
  11. CDA LEVEL I 数据分析认证考试模拟题库(一)
  12. 【VBA宏编程】——Excel操作
  13. 4.10招商银行笔试编程题
  14. 捋一捋Android的转场动画
  15. 2014春晚小品《扶不扶》台词集锦
  16. 贺州旱改水新丰景 国稻种芯·中国水稻节:广西改出乡村振兴
  17. 2020.10.3--PS--模糊工具、减淡工具、橡皮擦工具
  18. 【Unity3D进阶4-4】Unity3D AR的开发
  19. 【Markdown基础教程】Markdown介绍
  20. 1032 挖掘机技术哪家强 (20分) 测试点4

热门文章

  1. 关于研究网站开发还是应用程序的思考
  2. 国外值得关注的网站系列之二-社交化推荐网站GetGlue
  3. 黑客攻防技术宝典Web实战篇第2版—第11章 攻击应用程序逻辑
  4. RabbitAdmin 实战
  5. 机器学习预测信贷风险
  6. 一文理解设计模式之--观察者模式(Observer)
  7. php高德地图计算距离接口,路径长度-距离/面积计算-示例中心-JS API 示例 | 高德地图API...
  8. Docker的镜像基本原理和概念
  9. Eureka获取注册信息
  10. Java会话技术之 —— cookie与session