spring3.0 aop 获取 ibatis 执行的语句_Mybatis 源码分析:执行器
一、Jdbc的执行过程
一个简单的入门Demo:
public static final String URL = "jdbc:mysql://127.0.0.1:3306/mybatis?serverTimezone=GMT";public static final String USERNAME = "root";public static final String PASSWORD = "root";@Testpublic void streamTest() throws SQLException, ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD); String sql = "select id, order_no, user_name from order_info where id = 1"; Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(sql); while(rs.next()){ System.out.println("user_name:" + rs.getString(3)); } rs.close(); statement.close(); connection.close();}
二、Jdbc的三种执行器
我们平时最常使用的是PreparedStatment 执行器,因为他能够防止sql注入。在Statement每次执行都是一条条的静态Sql,而PreparedStatement 是一条sql和若干组参数。这样在多次执行的时候,数据的体量较小,所以理论上来性能会更高。当然Statement也有他的优势,就是在一个Statement中,可以执行不同的静态sql。
@Testpublic void preparedStatementTest() throws SQLException { Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD); String sql = "select * from order_info where id = ?"; PreparedStatement ps = connection.prepareStatement(sql); ps.setInt(1, 1); ps.execute(); printResultSet(ps); ps.setInt(1, 2); ps.execute(); printResultSet(ps); ps.close();}private void printResultSet(PreparedStatement ps) throws SQLException { ResultSet rs = ps.getResultSet(); while (rs.next()){ System.out.println(rs.getString(3)); } rs.close();}
三、Mybatis 的执行过程
1、SQL会话SqlSession
org.apache.ibatis.session.SqlSession 接口提供了基本Api,包含了select、select*、insert、update、delete、commit、flushStatements、rollback等。其本身不做任何业务逻辑的处理,所有的处理都交给执行器。是一个比较典型的门面模式设计。
2、Mybatis执行器
Executor核心作用是处理SQL请求、事物管理、维护缓存以及批处理等 。执行器在的角色更像是一个管理员,接收SQL请求,然后根据缓存、批处理等逻辑来决定如何执行这个SQL请求。并交给JDBC处理器执行具体SQL。
3、声明处理器
StatementHandler对应jdbc的三种执行器。他的作用就是用于通过JDBC处理SQL语句与参数。在会话中每调用一次CRUD,JDBC处理器就会生成一个实例与之对应(命中缓存除外)。
四、Executor执行器
1、基础执行器
BaseExecutor 基础执行器主要是用于维护缓存(一级缓存)和事务。
在我们的Sql会话中,都会调用BaseExecutor 的Query 与 Update 方法。BaseExecutor 正是通过这两个方法来处理的一级缓存逻辑。在Query方法中根据SQL及参数判断缓存中是否存在数据,有就走缓存,否则就会调用子类的doQuery() 方法去查询数据库,然后再设置到缓存中。Update 方法主要用于去清空缓存。
事务则是通过会话中调用commit、rollback 方法进行管理。
2、简单执行器
SimpleExecutor是默认执行器,它的行为是每处理一次会话当中的SQl请求都会通过对应的StatementHandler 构建一个新的Statement,这就会导致即使是相同SQL语句也无法重用Statement。
@Testpublic void simpleTest() throws SQLException { // 获取构建器 SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); // 解析XML 并构造会话工厂 SqlSessionFactory factory = factoryBuilder.build(ExecutorTest.class.getResourceAsStream("/mybatis-config.xml")); Configuration configuration = factory.getConfiguration(); JdbcTransaction jdbcTransaction = new JdbcTransaction(factory.openSession().getConnection()); SimpleExecutor executor = new SimpleExecutor(configuration, jdbcTransaction); // 获取SQL映射 MappedStatement ms = configuration.getMappedStatement("com.ycdhz.mybatis.dao.OrderInfoDao.queryById"); executor.doQuery(ms, 10, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10)); executor.doQuery(ms, 10, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));}
查询执行记录,我们发现SimpleExecutor每次都会创建一个新的预处理器。
==> Preparing: select id, order_no, user_name, create_dt, product_no, product_count from order_info where id =? ==> Parameters: 1(Integer)<== Total: 1==> Preparing: select id, order_no, user_name, create_dt, product_no, product_count from order_info where id =? ==> Parameters: 10(Integer)<== Total: 1
这个的主要原因就在于SimpleExecutor.doQuery中的prepareStatement方法,每一次查询的时候都会去重新获取以一个新的Statement。
package org.apache.ibatis.executor;public class SimpleExecutor extends BaseExecutor { 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; }}
3、可重用执行器
ReuseExecutor 区别在于他会将在会话期间内的Statement进行缓存,并使用SQL语句作为Key。所以当执行下一请求的时候,不在重复构建Statement,而是从缓存中取出并设置参数,然后执行。
这也说明为啥执行器不能跨线程调用,这会导致两个线程给同一个Statement 设置不同场景参数。
@Testpublic void resuseTest() throws SQLException { // 获取构建器 SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); // 解析XML 并构造会话工厂 SqlSessionFactory factory = factoryBuilder.build(ExecutorTest.class.getResourceAsStream("/mybatis-config.xml")); Configuration configuration = factory.getConfiguration(); JdbcTransaction jdbcTransaction = new JdbcTransaction(factory.openSession().getConnection()); MappedStatement ms = configuration.getMappedStatement("com.ycdhz.mybatis.dao.OrderInfoDao.queryById"); ReuseExecutor executor = new ReuseExecutor(configuration, jdbcTransaction); executor.doQuery(ms, 10, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10)); // 相同的SQL 会缓存对应的 PrepareStatement-->缓存周期:会话 executor.doQuery(ms, 10, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));}
查询执行记录,我们发现ReuseExecutor相同的SQL只执行一次预处理(在ReuseExecutor中即使是使用不同的MappedStatement,只要两个方法的sql相同那么他都会命中jdbc的Statement缓存)。
==> Preparing: select id, order_no, user_name, create_dt, product_no, product_count from order_info where id =? ==> Parameters: 1(Integer)<== Total: 1==> Parameters: 10(Integer)<== Total: 1
这个的主要原因是因为ReuseExecutor.doQuery中的prepareStatement方法,会从jdbc的Statement缓存中检查是否有Sql的预编译,如果有直接获取。没有则重新创建并put到Statement缓存中。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); //去检查一级缓存中,是否存在对应的PrepareStatement if (hasStatementFor(sql)) { stmt = getStatement(sql); applyTransactionTimeout(stmt); } else { Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); putStatement(sql, stmt); } handler.parameterize(stmt); return stmt;}
4、批处理执行器
BatchExecutor 顾名思议,它就是用来作批处理的。会将所有的SQL请求集中起来,最后调用Executor.flushStatements() 方法时一次性将所有请求发送至数据库。同时批处理只对修改有效,对查询是无效的。BatchEexecutor中的查询等同于SimpleExecutor的doQuery方法。
@Testpublic void batchTest() throws SQLException { // 获取构建器 SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); // 解析XML 并构造会话工厂 SqlSessionFactory factory = factoryBuilder.build(ExecutorTest.class.getResourceAsStream("/mybatis-config.xml")); Configuration configuration = factory.getConfiguration(); //factory.openSession(true) 开启自动提交功能 JdbcTransaction jdbcTransaction = new JdbcTransaction(factory.openSession(true).getConnection()); MappedStatement ms = configuration.getMappedStatement("com.ycdhz.mybatis.dao.OrderInfoDao.update"); BatchExecutor executor = new BatchExecutor(configuration, jdbcTransaction); Map<Object, Object> param = new HashMap(); param.put("0",1); param.put("1","jany1234"); executor.doUpdate(ms, param); executor.doUpdate(ms, param); executor.doFlushStatements(false);// executor.commit(true);}
提交修改必须要执行flushStatements才会生效(需要开启事务自动提交)。或者直接通过commit方法提交。
@Overridepublic void commit(boolean required) throws SQLException { if (closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } clearLocalCache(); flushStatements(); if (required) { transaction.commit(); }}
我们再来看一个例子,在这种情况下,batchResultList到底是多少呢?
@Testpublic void BatchExecutorTest() throws SQLException { SqlSession sqlSession = factory.openSession(ExecutorType.BATCH, true); OrderInfoDao orderInfoMapper = sqlSession.getMapper(OrderInfoDao.class); orderInfoMapper.update(1, "jany"); orderInfoMapper.update1(1, "jany"); orderInfoMapper.updateProductNo(2, "jany1"); orderInfoMapper.updateProductNo(3, "jany1"); orderInfoMapper.updateProductNo(4, "jany1"); orderInfoMapper.update(1, "jany5"); //在这种情况下,batchResultList到底是多少呢? List batchResultList = sqlSession.flushStatements(); System.out.println(batchResultList.size());}
我们看日志发现,实际上执行了四次。这是因为OpenSession会一次性将所有请求发送到BatchExecutor,BatchExecutor会将所有的sql语句存储到statementList中。为了保证数据的一致性,所以BatchExecutor会采用顺序执行。所以只有在同时满足sql相同、MappedStatement相同、且两条Sql是相邻的这三个条件的情况下会命中同一个Jdbc Statement。
==> Preparing: update order_info set user_name = ? where id = ? ==> Parameters: jany(String), 1(Integer)==> Preparing: update order_info set user_name = ? where id = ? ==> Parameters: jany(String), 1(Integer)==> Preparing: update order_info set product_no = ? where id = ? ==> Parameters: jany1(String), 2(Integer)==> Parameters: jany1(String), 3(Integer)==> Parameters: jany1(String), 4(Integer)==> Preparing: update order_info set user_name = ? where id = ? ==> Parameters: jany5(String), 1(Integer)
5、缓存执行器
CachingExecutor 是用于处理二级缓存的。二级缓存和一级缓存相对独立,同时二级缓存可以通过参数控制关闭,而一级缓存不可以。二级缓存采用了装饰者设计模式,即在CachingExecutor 对原有的执行器进行包装,处理完二级缓存逻辑之后,把SQL执行相关的逻辑交给实至的Executor处理。
第一步:需要通过注解@CacheNamespace开启二级缓存;
第二步:执行common方法提交。二级缓存与一级缓存不同,他必须要提交之后才会更新缓存;
@Testpublic void cachTest() throws SQLException{ // 获取构建器 SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); // 解析XML 并构造会话工厂 SqlSessionFactory factory = factoryBuilder.build(ExecutorTest.class.getResourceAsStream("/mybatis-config.xml")); Configuration configuration = factory.getConfiguration(); JdbcTransaction jdbcTransaction = new JdbcTransaction(factory.openSession().getConnection()); MappedStatement ms = configuration.getMappedStatement("com.ycdhz.mybatis.dao.OrderInfoDao.queryById"); SimpleExecutor simpleExecutor = new SimpleExecutor(configuration, jdbcTransaction); Executor executor = new CachingExecutor(simpleExecutor);//装饰者模式 executor.query(ms, 1, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER); // 必须要提交以后才会更新二级缓存 executor.commit(true); executor.query(ms, 1, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER);}
spring3.0 aop 获取 ibatis 执行的语句_Mybatis 源码分析:执行器相关推荐
- 深入理解Spark 2.1 Core (七):Standalone模式任务执行的原理与源码分析
这篇博文,我们就来讲讲Executor启动后,是如何在Executor上执行Task的,以及其后续处理. 执行Task 我们在<深入理解Spark 2.1 Core (三):任务调度器的原理与源 ...
- 【spring】spring异步执行的使用与源码分析
在实际的开发过程中,有些业务逻辑使用异步的方式处理更为合理.比如在某个业务逻辑中,需要把一些数据存入到redis缓存中,这个操作只是一个辅助的功能,成功或者失败对主业务并不会产生根本影响,这个过程可以 ...
- 线上问题:stream获取值抛出空指针及源码分析
1 场景复现 实体列表,通过stream获取数据,findFirst后,直接使用get获取数据,抛出空指针异常,复现代码如下: @Testpublic void streamGetUnsafeTest ...
- 《MySQL8.0.22:Lock(锁)知识总结以及源码分析》
目录 1.关于锁的一些零碎知识,需要熟知 事务加锁方式: Innodb事务隔离 MVCC多版本并发控制 常用语句 与 锁的关系 意向锁 行级锁 2.锁的内存结构以及一些解释 3.InnoDB的锁代码实 ...
- MyBatis 源码分析 - SQL 的执行过程
本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析.运 ...
- PHP获取MySQL执行sql语句的查询时间
PHP获取MySQL执行sql语句的查询时间 1. $t1=microtime(true); mysql_query($sql); echo microtime(true)-$t1; 2. //计时开 ...
- Spring AOP 源码分析 - 拦截器链的执行过程
1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...
- mysql update语句卡死_oracle执行update语句时卡住问题分析及解决办法
问题 开发的时候debug到一条update的sql语句时程序就不动了,然后我就在plsql上试了一下,发现plsql一直在显示正在执行,等了好久也不出结果.但是奇怪的是执行其他的select语句却是 ...
- Spring AOP 源码分析 - 创建代理对象
1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...
最新文章
- python在坐标轴上画矩形_Python使用matplotlib实现在坐标系中画一个矩形的方法
- 4.3、Libgdx启动类和配置
- 对软件测试工程师面试题目的回答[转]
- 后端程序员必备:书写高质量SQL的30条建议
- 牛客 contest897 C-Latale(树上dp)
- es6解决回调地狱问题
- linux kset subsystem 3.10内核,Kobject、Kset 和 Subsystem
- 2020款iPhone SE最快下周发布:价格3000以内
- linux 时间怎么求差值_linux 时间戳及时间差计算
- Beyond Compare Pro for Mac
- JS浏览器兼容性问题
- 交换两个数 不使用中间变量
- PMP资料,考过的学员整理分享
- bat计算机清理原理,电脑如何一键清除垃圾bat
- html图片隐藏文字,怎样用CSS隐藏图片背景的文字内容
- 【算法刷题日记之本手篇】井字棋与密码强度等级
- 解决h5中video标签返回流无法快进和后退的问题
- HTTPS单向认证双向认证
- java面条对折问题
- php 兼容火狐,HTML_总结CSS中火狐浏览器与IE浏览器的兼容代码,如何让你写的代码更兼容火狐 - phpStudy...