一、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 源码分析:执行器相关推荐

  1. 深入理解Spark 2.1 Core (七):Standalone模式任务执行的原理与源码分析

    这篇博文,我们就来讲讲Executor启动后,是如何在Executor上执行Task的,以及其后续处理. 执行Task 我们在<深入理解Spark 2.1 Core (三):任务调度器的原理与源 ...

  2. 【spring】spring异步执行的使用与源码分析

    在实际的开发过程中,有些业务逻辑使用异步的方式处理更为合理.比如在某个业务逻辑中,需要把一些数据存入到redis缓存中,这个操作只是一个辅助的功能,成功或者失败对主业务并不会产生根本影响,这个过程可以 ...

  3. 线上问题:stream获取值抛出空指针及源码分析

    1 场景复现 实体列表,通过stream获取数据,findFirst后,直接使用get获取数据,抛出空指针异常,复现代码如下: @Testpublic void streamGetUnsafeTest ...

  4. 《MySQL8.0.22:Lock(锁)知识总结以及源码分析》

    目录 1.关于锁的一些零碎知识,需要熟知 事务加锁方式: Innodb事务隔离 MVCC多版本并发控制 常用语句 与 锁的关系 意向锁 行级锁 2.锁的内存结构以及一些解释 3.InnoDB的锁代码实 ...

  5. MyBatis 源码分析 - SQL 的执行过程

    本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析.运 ...

  6. PHP获取MySQL执行sql语句的查询时间

    PHP获取MySQL执行sql语句的查询时间 1. $t1=microtime(true); mysql_query($sql); echo microtime(true)-$t1; 2. //计时开 ...

  7. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  8. mysql update语句卡死_oracle执行update语句时卡住问题分析及解决办法

    问题 开发的时候debug到一条update的sql语句时程序就不动了,然后我就在plsql上试了一下,发现plsql一直在显示正在执行,等了好久也不出结果.但是奇怪的是执行其他的select语句却是 ...

  9. Spring AOP 源码分析 - 创建代理对象

    1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...

最新文章

  1. python在坐标轴上画矩形_Python使用matplotlib实现在坐标系中画一个矩形的方法
  2. 4.3、Libgdx启动类和配置
  3. 对软件测试工程师面试题目的回答[转]
  4. 后端程序员必备:书写高质量SQL的30条建议
  5. 牛客 contest897 C-Latale(树上dp)
  6. es6解决回调地狱问题
  7. linux kset subsystem 3.10内核,Kobject、Kset 和 Subsystem
  8. 2020款iPhone SE最快下周发布:价格3000以内
  9. linux 时间怎么求差值_linux 时间戳及时间差计算
  10. Beyond Compare Pro for Mac
  11. JS浏览器兼容性问题
  12. 交换两个数 不使用中间变量
  13. PMP资料,考过的学员整理分享
  14. bat计算机清理原理,电脑如何一键清除垃圾bat
  15. html图片隐藏文字,怎样用CSS隐藏图片背景的文字内容
  16. 【算法刷题日记之本手篇】井字棋与密码强度等级
  17. 解决h5中video标签返回流无法快进和后退的问题
  18. HTTPS单向认证双向认证
  19. java面条对折问题
  20. php 兼容火狐,HTML_总结CSS中火狐浏览器与IE浏览器的兼容代码,如何让你写的代码更兼容火狐 - phpStudy...

热门文章

  1. 【lucene】lucene高亮显示
  2. 【kafka】Kafka常用JMX监控指标整理
  3. 还在用Swagger?我推荐这款零代码侵入的接口管理神器!
  4. 小妙招:如何防止你的 jar 包被反编译?
  5. JMeter 做接口性能测试,YYDS!
  6. 校招生大规模涨薪、再扩招10000人?大厂抢人有多野?
  7. SQL Server 2008 R2 安装
  8. 静电场与电流场_2021高考复习:静电场考点突破微专题11 带电粒子在交变电场中的运动 9.26...
  9. Pycharm文档模板变量
  10. 20190501-整数翻转