共计 6253 个字符,预计需要花费 16 分钟才能阅读完成。
本篇来聊一下 mybatis 的执行器,看看如何在不同场景应用不同执行器以及不同执行器的实现原理是怎么的(基于 mybatis 3.4.6)。
知识点
- 什么是执行器
- mybatis 执行器类型及何时应用
-
各个执行器的实现原理
什么是执行器
顾名思义,执行器就是用来执行咱们的 sql 语句,从而取到后果或对数据库进行更新的货色。它是 mybatis 中十分外围的概念,它提供了增、改、查、事务管理等基本操作接口,基本上所有的货色都是围绕执行器来进行,看下图
mybatis 执行器类型及何时应用
执行器类型
咱们先来看下 mybatis 目前有哪些执行器类型
- SIMPLE
- REUSE
-
BATCH
咱们能配置的目前就以上三种执行器,通过 defaultExecutorType(留神是全局的)来指定应用哪种,如果是通过 xml 形式配置的,则参照官网
如果是应用 spring boot 集成的,则如下
上面咱们别离对三种执行器来一一介绍。SIMPLE
能够了解为根本的执行器,这也是 mybatis 的默认执行器,也是咱们平时用的最多的执行器,咱们无需做任何配置更改。它大抵的流程是:关上连贯 -> 设置 Statement -> 参数注入 -> 执行 -> 后果映射 -> 敞开 Statement,能够看到每次用完之后会把 Statement 关掉。
REUSE
看名字就晓得这是一个可重用执行器,什么叫可重用呢?指的是 simple 流程中的后面两步能够反复利用,也就是 关上连贯 -> 设置 Statement,这两步会创立一个新的 Statement,reuse 执行器外部保护一个缓存,第一次获取后就会以对应的 sql(占位符 ? 不被具体参数替换)作为 key 进行缓存该 Statement,并且不对 Statement 进行敞开,前面遇到该 sql 只有从第三步开始做就能够了,这样就带来了性能上的优化(实际上优化并不大)。
BATCH
同样通过名称就能看进去这是一个批量执行器,批量是什么意思?就是说它是能够一次性执行一批 sql 语句的,次要是针对更新、插入等批改性操作,对于单条或者查问类操作,就不要用这个执行器了,为什么能做到批量执行呢,其实实质上是用的 sql 包里的
PreparedStatement
的addBatch()
,前面原理局部再细说。何时应用
分明了三种执行器类型的特点之后,咱们再来比照下在不同场景下的性能输入,这样大家就分明何时应用何种类型执行器了,这里没有比照更新操作,是因为插入操作实质上用的就是更新操作接口。基于 spring 应用的 mybatis,基于以下表构造
单条查问
先上代码
DefaultSqlSessionFactory sqlSessionFactory = (DefaultSqlSessionFactory)applicationContext.getBean("sqlSessionFactory"); // 这里本人抉择执行器类型 DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.SIMPLE); long startTime = System.currentTimeMillis(); // userInfoMapper.selectById(22222); defaultSqlSession.selectList("com.example.mybatisanalyze.mapper.UserInfoMapper.selectById", 22222); long endTime = System.currentTimeMillis(); System.out.println((endTime - startTime) + "ms");
simple:
从图中能够看出花了 42ms
reuse:
将DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.SIMPLE);
改为DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.REUSE);
从图中能够看出花了 34ms
batch:
将DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.SIMPLE);
改为DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.BATCH);
从图中能够看出花了 43ms
论断:单条查问三种执行器差不多,然而倡议抉择 simple,起因见执行器类型中阐明。单条插入
先上代码
DefaultSqlSessionFactory sqlSessionFactory = (DefaultSqlSessionFactory)applicationContext.getBean("sqlSessionFactory"); // 执行器类型本人抉择 DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.SIMPLE); long startTime = System.currentTimeMillis(); UserInfo userInfo = new UserInfo(); userInfo.setNickName("bbbcd"); userInfo.setUserName("abcdc"); userInfo.setBirthday(new Date()); userInfo.setRegisterTime(LocalDateTime.now()); userInfo.setEmail("12345"); defaultSqlSession.insert("com.example.mybatisanalyze.mapper.UserInfoMapper.insert", userInfo); long endTime = System.currentTimeMillis(); System.out.println((endTime - startTime) + "ms");
simple:
从图中能够看出花了 172ms
reuse:
代码中DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.SIMPLE)
这行改为DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.REUSE);
从图中能够看出花了 178ms
batch:
代码中DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.SIMPLE)
这行改为DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.BATCH);
从图中能够看出花了 176ms
论断:三种执行器性能差不多,倡议抉择 simple批量插入
先上代码
DefaultSqlSessionFactory sqlSessionFactory = (DefaultSqlSessionFactory)applicationContext.getBean("sqlSessionFactory"); // 这里执行器类型本人抉择 DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.SIMPLE); long startTime = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) {UserInfo userInfo = new UserInfo(); userInfo.setNickName("bbbc"); userInfo.setUserName("abcd"); userInfo.setBirthday(new Date()); userInfo.setRegisterTime(LocalDateTime.now()); userInfo.setEmail("1234"); defaultSqlSession.insert("com.example.mybatisanalyze.mapper.UserInfoMapper.insert", userInfo); } long endTime = System.currentTimeMillis(); System.out.println((endTime - startTime) + "ms");
插入一万条数据
simple:
从图中能够看出花了 11762ms
reuse:
代码中DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.SIMPLE)
这行改为DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.REUSE);
从图中能够看出花了 10992ms
batch:
代码中DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.SIMPLE)
这行改为DefaultSqlSession defaultSqlSession = (DefaultSqlSession)sqlSessionFactory.openSession(ExecutorType.BATCH);
并且在 for 循环之后加个 commit 操作defaultSqlSession.commit();
从图中能够看出花了 6173ms
论断:显著应该抉择 batch各个执行器的实现原理
mybatis 和执行器相干的逻辑都在
org.apache.ibatis.executor
包下
先来看一下执行器接口org.apache.ibatis.executor.Executor
能够看到根本就是数据库相干操作,再来看下继承体系
能够看到咱们所用的三种执行器都是子类,这里用到两种设计模式,别离是BaseExecutor
中实现的模板模式和CachingExecutor
中实现的装璜器模式。咱们从入口开始看,入口在org.apache.ibatis.session.defaults.DefaultSqlSession
,这里提供了根本的操作,咱们基于selectOne
办法进行剖析,始终跟到org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
会发现他实质调的就是执行器的查问操作
而这个执行器哪里生成的呢?在org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
中咱们调用openSession
办法时,咱们传入指定的执行器类型,也能够应用默认的,最终会在org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
中创立执行器
这里能够看到会依据传入的执行器类型来创立对应执行器,默认应用 simple,会应用CachingExecutor
来进行一层包装。接着下面的 query 函数持续剖析,咱们晓得它默认会走到org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
这里应用了装璜器模式,在原来的执行器性能根底上增加了二级缓存的性能。外面最终是调用org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
进行执行,这里用到了一级缓存,没有缓存的状况下会去数据库查,也就是调用办法queryFromDatabase
这里就应用了模板模式,doQuery()
由子类本人来实现,各个子类执行器的逻辑绝对比较简单,这里就介绍一下BatchExecutor
,其余能够本人去看
这里保护了statementList
和batchResultList
来记录执行的指令以及寄存对应后果的对象,在doUpdate
的时候进行记录,最终会在handler.batch()
中去调用PreparedStatement
的addBatch
办法,相当于把指令暂存到预编译器中。后续在咱们做 commit()的时候会去调用doFlushStatements
办法去做批执行。总结
本篇文章的干货还是比拟多的,基本上将 mybatis 的执行器进行了比拟具体的介绍以及如何选型,当然底层还波及到连接池和 sql 驱动包的一些知识点,连接池前面讲,sql 预编译器批量执行的介绍,能够参考 https://blog.csdn.net/bluelin…