总结于B站鲁班大叔视频:https://www.bilibili.com/video/BV1Tp4y1X7FM?p=13&spm_id_from=pageDriver

概述

JDBC的执行流程可以大致分为:

  1. 获得连接
  2. 预编译sql
  3. 设置参数
  4. 执行sql

Mybatis执行原理大致分为:

  1. 动态代理MapperProxy
  2. sql会话Sqlsession
  3. 执行器Executor
  4. JDBC处理器StatementHandler

图中JDBC圈起来的部分就对应了sql具体的执行过程,属于Mybatis执行器范围内。本文主要针对sql会话与执行器来展开讨论。

Mybatis的执行过程

Mybatis使用门面模式提供了统一的门面接口API:

  • 基本的API:增、删、改、查等
  • 辅助的API:提交、关闭会话等

这些统一的API就是Sqlsession提供的,具体的API实现交由执行器来完成

Executor的实现

  • SimpleExecutor(默认实现)

    • 其每次执行sql,无论sql是否一样,都会进行sql的预处理
  • ReuseExecutor(可复用MappedStatement,MappedStatement包装的是sql信息)
    • 如果多次执行的sql一致,只会进行一次sql预处理
    • sql的重用,之和sql有关,即sql和参数一致,就可以重用,这个很重要
  • BatchExecutor(批处理)
    • 只针对增、删、改操作,也就是修改操作。如果是查询语句,则和SimpleExecutor没有区别。
    • 在批处理的情况下,对相同的sql,只会预处理一次,多次设置sql的参数值。
    • 必须手动调用BatchExecutor.doFlushStatement()方法提交事务。
  • BaseExecutor(执行器抽象类)
    • 实现SimpleExecutor、BatchExecutor、ReuseExecutor三者的重复操作:一级缓存、获取连接等
    • SimpleExecutor、BatchExecutor、ReuseExecutor继承BaseExecutor,BaseExecutor实现Executor
    • 定义query和doUpdate方法供SimpleExecutor、BatchExecutor、ReuseExecutor使用。

一级缓存

由于一级缓存、获取连接的实现在BaseExecutor中,单独使用SimpleExecutor、BatchExecutor、ReuseExecutor就无法得到BaseExecutor的支持

  • BaseExecutor中query方法会创建缓存的key,并调用其重载的方法query方法,通过localcache(key)去获取一级缓存,如果没有缓存,就会去调用doQuery方法,这个【doQuery】方法就是子类(SimpleExecutor、BatchExecutor、ReuseExecutor)中实现的数据库操作方法

二级缓存

  • CachingExecutor

    • 实现Executor接口
    • 只专注实现二级缓存的逻辑
    • CachingExecutor二级缓存的逻辑执行完成之后,将会把业务交由下一个执行器处理。下一个执行器(SimpleExecutor或BatchExecutor或ReuseExecutor)由构造方法指定。装饰者模式。

需要注意的是:

  • 一级缓存的数据是开始执行的时候就生成了,二级缓存的数据是提交执行过后才会生成
  • 之后的查询会先访问二级缓存,再访问一级缓存。

缓存命中场景

一级缓存

一级缓存是key-value形式的,实质底层就是一个HashMap。作用范围是sqlsession,当数据库会话结束之后,随之消亡

命中条件

  • sql和参数相同。
  • 相同的statementID(需要调用相同mapper中的查询方法相同)
  • 同一个sqlsession(这就是为什么一级缓存也叫会话级缓存)
  • 如果是分页查询,使用的RowBounds也要相同(也就是查询起始行、查询数据大小都要相同)

影响命中一级缓存的设置

  • 手动清空sqlsession.clearCache()
  • Mapper层(DAO层)查询方法使用了@Options(fluashCache = Opions.FlushCachePolicy.TRUE)
    • 该参数设置在每次查询后都会清空一级缓存
  • 执行了update操作
  • rollback也会清空相应的缓存

Spring集成mybatis一级缓存失效问题

spring集成Mybatis之后,不满足同一个sqlsession,导致一级缓存失效。在没有配置事务的情况下,每次执行sql都会构造一个新的会话。是由于srping的动态代理导致的。

解决办法就是将sql执行放在同一个事务中,就会使用同一个sqlsession,同一个sqlsession的情况下,一级缓存就不会失效

二级缓存

为什么已经有了一级缓存,还需要二级缓存呢?

二级缓存也称作是应用级缓存,与一级缓存不同的是它的作用范围是整个应用,而且可以跨线程使用。所以二级缓存有更高的命中率,适合缓存一些修改较少的数据

  • 二级缓存可以使用内存和磁盘进行存储。
  • 溢出淘汰:在有限的内存中缓存数据,始终会面临缓存承载满的情况,就会存在缓存淘汰机制
    • FIFO,先进先出的淘汰算法。
    • LRU,最近最少使用的淘汰机制。
  • 缓存设置过期时间,会进行过期清理。
  • 线程安全
  • 命中率统计
  • 序列化

Mybatis二级缓存设计、

接口定义:

实现类

Mybatis使用装饰器+责任链模式构建了完整的二级缓存结构:

  • SynchronizedCache:线程同步
  • LoggingCache:记录命中率
  • LruCache:防止溢出
  • ScheduledCache:过期清理
  • BlockingCache:防止内存穿透
  • PerpetualCache:内存存储

调用Cache实现类的任何方法都会沿着上面的xxxCache完成装饰,并执行

二级缓存命中条件

  • 必须提交了事务

    • 即使是设置了autocommite也不能命中缓存,除非是手动调用了commite方法,或者是关闭当前的事务
  • sql和参数相同。
  • 如果是分页查询,使用的RowBounds也要相同(也就是查询起始行、查询数据大小都要相同)

影响二级缓存命中的设置

  • 缓存的开关userCache = true。
  • Mapper层(DAO层)查询方法使用了@Options(fluashCache = Opions.FlushCachePolicy.TRUE)
    • 该参数设置在每次查询后都会清空一级缓存
  • 声明缓存空间
    • <cache><cache/>或者@CacheNamespace
    • 同时必须引用缓存空间:<cache-ref>或@CacehNamespaceRef

为什么要提交之后才能命中二级缓存?

因为二级缓存是跨线程使用的

例如sqlsessionA和sqlsessionB去查询同样的数据

  • A先修改了数据,再对该数据进行查询
  • B去查询该数据将结果填充进入二级缓存
  • A去查询该数据的时候,B进行的回滚
  • A这时候如果从二级缓存读取数据,就会产生脏读

所有执行结果在commit之前,都会放入事务缓存管理器的暂存区,只有被commit之后才会放入缓存区,这里的缓存区指的就是装饰器+责任链模式构建了完整的二级缓存结构。

StatementHandler

JDBC处理器,基于JDBC构建的Statement并设置参数,然后执行sql,没调用会话当中的一次sql,就有与之相对应的且唯一的Statement实例。

主要功能

  • 声明(创建)Statement

    • prepare方法基于Connection创建Statement
  • 设置参数
  • 查询
  • 修改

主要是实现:

  • BaseStatementHandler(从子类处理器中抽象出公共的处理逻辑)
  • SimpleStatementHandler(简单处理器)
  • PreparedStatementHandler(预处理器)
    • 大多数情况下都是使用该处理器,预处理性能更高,会进行参数转义,防止sql注入
  • CallableStatementHandler(存储过程处理器)

PreparedStatementHandler执行流程

这个过程包括从 执行器 -> StatementHandler -> 参数处理 -> 结果集处理

  • 执行器
  • StatementHandler
    • 预编译
    • 设置参数
    • 执行sql
    • 结果集映射成Java bean

声明或者是创建Statement

执行查询的开始代码,是在具体Executor的实现类中的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();//创建StatementStatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {//创建Statement实例StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}//RoutingStatementHandler构造方法根据Statement类型创建具体的Statementpublic 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());}}

创建完Statement之后,回到doQuery方法,调用prepareStatement()方法

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);//创建Statementstmt = handler.prepare(connection, transaction.getTimeout());//设置参数handler.parameterize(stmt);return stmt;}@Overridepublic Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());Statement statement = null;try {//instantiateStatement抽象方法由具体的子类实现(PreparedStatementHandler、SimpleStatementHandler、CallableStatementHandler)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);}}

回到prepareStatement方法,handler.parameterize(stmt);设置参数。parameterize方法是各个StatementHandler实现类的具体实现。最后回到doQuery方法通过具体的handler的query方法执行真正的sql操作




Mybatis的运行原理

获取SqlSessionFactory对象

  1. 创建SqlSessionFactory实际上就是加载配置信息,包括Mybatis的配置文件以及加载Mapper文件。
  2. 将Mapper文件中的sql解析为MappedStatement(一个MappedStatement对应一个mapper文件中的sql) 以及 配置信息封装为Configuration。
  3. Configuration注入DefaultSqlSessionFactory返回。

OpenSession

DefaultSqlSessionFactory的openSession

  @Overridepublic SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {//获取配置信息final Environment environment = configuration.getEnvironment();//创建事务final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//根据执行器类型创建执行器final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

获取查询的代理对象

openSession执行之后返回了DefaultSqlSession,DefaultSqlSession用来获取查询的代理对象。其实DefaultSqlSession已经可以根据之前加载的配置信息以及MappeStatement进行查询了。

DefaultSqlSession的getMapper

  @Overridepublic <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);}

Configuration

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}

MapperRegistry

  private final Configuration config;private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {//根据Mapper的代理工厂构建Mapper的代理对象return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}

MapperProxyFactory

  public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}protected T newInstance(MapperProxy<T> mapperProxy) {//JDK动态代理return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}

最终返回的Mapper

执行sql

执行流程就是执行器Executor的原理的,中间穿插了一级缓存和二级缓存的逻辑。

  1. 代理对象包含了DefaultSqlSession,实际执行查询也是通过DefaultSqlSession
  2. DefaultSqlSession的创建中也包含了具体的执行器,通过执行器的doQuery进行实际查询
  3. 具体的Executor创建其对应的StatementHandler,进行参数的设置,预编译(ParameterHandler),执行查询(StatementHandler),返回结果处理(ResultSetHandler)

Mybatis原理——执行原理详解相关推荐

  1. DeepLearning tutorial(4)CNN卷积神经网络原理简介+代码详解

    FROM: http://blog.csdn.net/u012162613/article/details/43225445 DeepLearning tutorial(4)CNN卷积神经网络原理简介 ...

  2. Nginx(二):反向代理原理 与 配置文件详解

    相关阅读: Nginx(一):Nginx原理概述 与 安装步骤详解 Nginx(二):反向代理原理 与 配置文件详解 Nginx(三):负载均衡策略 与 Nginx静态服务器 Nginx(四):Ngi ...

  3. Spark SQL原理及常用方法详解(二)

    Spark SQL 一.Spark SQL基础知识 1.Spark SQL简介 (1)简单介绍 (2)Datasets & DataFrames (3)Spark SQL架构 (4)Spark ...

  4. java的markword_【转帖】Java工具结构与锁实现原理及MarkWord详解

    Java工具结构与锁实现原理及MarkWord详解 https://www.pianshen.com/article/2382167638/ 我们都知道,Java工具存储在堆(Heap)内存.那么一个 ...

  5. 【胖虎的逆向之路】04——脱壳(一代壳)原理脱壳相关概念详解

    [胖虎的逆向之路]04--脱壳(一代壳)原理&脱壳相关概念详解 [胖虎的逆向之路]01--动态加载和类加载机制详解 [胖虎的逆向之路]02--Android整体加壳原理详解&实现 [胖 ...

  6. Cookie的工作原理和应用详解

    Cookie的工作原理和应用详解 1. Cookie 原理 1.1 Cookie 背景信息 1.2 Cookie 工作原理 1.3 Cookie 创建.获取.修改 1.4 Cookie 共享范围 1. ...

  7. Pytorch | yolov3原理及代码详解(二)

    阅前可看: Pytorch | yolov3原理及代码详解(一) https://blog.csdn.net/qq_24739717/article/details/92399359 分析代码: ht ...

  8. 晶振工作原理及参数详解

    本文转载于:https://www.cnblogs.com/sunshine-jackie/p/8137293.html 晶振工作原理及参数详解(最透彻) 晶振工作原理及参数详解(最透彻) 原文链接点 ...

  9. Dubbo 原理和机制详解

    Dubbo 是一款Java RPC框架,致力于提供高性能的 RPC 远程服务调用方案.作为主流的微服务框架之一,Dubbo 为开发人员带来了非常多的便利. 1. Dubbo核心功能 Dubbo主要提供 ...

  10. redis队列优先级java实现_Redis 实现队列原理的实例详解

    Redis 实现队列原理的实例详解 场景说明: ·用于处理比较耗时的请求,例如批量发送邮件,如果直接在网页触发执行发送,程序会出现超时 ·高并发场景,当某个时刻请求瞬间增加时,可以把请求写入到队列,后 ...

最新文章

  1. 电子商务(电销)平台中用户模块(User)数据库设计明细
  2. 去哪编辑html5页面,h5页面 判断网页在哪打开
  3. 绝了!Pandas绘图功能
  4. [开源]Dapper Repository 一种实现方式
  5. 安装Windows Nano Server虚拟机
  6. 清明节特辑 |记忆存储、声音还原、性格模仿……AI可以让人类永生吗?
  7. 【Pre蓝桥杯嵌入式】【STM32】Unkown device
  8. 拿完年终奖换工作?频繁跳槽职场人工资低于同龄人平均水平
  9. 浅谈Rem 及其转换原理
  10. 使用windows2008R2自带磁盘管理进行分区
  11. 【负载观测】永磁同步电机的负载观测及前馈补偿
  12. 算法工程师必备技能(Python 优化提速小技巧)
  13. 微信小程序之设置背景图片
  14. FineReport帆软报表使用入门
  15. iOS开发-Xcode8兼容iOS7手记
  16. android twitter 分享代码,Twitter分享集成
  17. jeecg ajax验证,jeecg权限模块学习
  18. 一道关于SVM的机器学习作业题
  19. nacos 未读取到合法数据,请检查导入的数据文件
  20. 手机android系统问题怎么解决方案,4解决Android系统崩溃问题的解决方案

热门文章

  1. 彻底解决WPS右键没有新建文件的问题
  2. 页面生命周期:DOMContentLoaded, load, beforeunload, unload解析
  3. 电脑文件误删除如何恢复?可以快速找回
  4. mysql 1093 you can_mysql中错误:1093-You can’t specify target table for update in FROM clause的解决方法...
  5. ansible aws_如何使用Ansible管理您的AWS资源
  6. 解决网络丢包问题及故障判断方法
  7. 验证码之google的reCAPTCHA使用
  8. 据说很多搞软件的羡慕硬件工程师
  9. java3d关闭透视,3DMax怎么去除透视效果?3D新手请详解?
  10. was部署java项目_web工程was部署