硬核!想要了解MyBatis执行器的小伙伴必看!

深入理解MyBatis执行器的设计理念

文章目录

      • 深入理解MyBatis执行器的设计理念
        • JDBC中的statement类型
          • 关于Statement简单执行器和PreparedStatement预处理执行器
        • MyBatis的执行过程是怎样的?
        • SqlSession
        • Executor
        • Executor的种类

JDBC中的statement类型

  1. Statement(简单执行器)

    作用:执行静态SQL,执行批处理、设置每次抓取的行数。

  2. PreparedStatement(预处理执行器)

    继承Statement接口,对SQL语句进行预编译,防止SQL注入。

  3. CallableStatement(存储过程执行器)

    继承PreparedStatement接口,用于调用数据库的存储过程,设置出参、读取出参。

    注意:三个都是接口

关于Statement简单执行器和PreparedStatement预处理执行器

使用statement对象的优缺点

优点:可以一次性向数据库发送多条不同的SQL语句或者相同的SQL语句但参数不同,大大提高了效率。

缺点:一次传输的数据量相较于PreparedStatement的批处理要大

使用PreparedStatement对象的优缺点

优点:针对相同的SQL语句,只需要发送一次SQL语句,再发送一个参数组(包含着不同的参数)即可,数据量较小。

缺点:只适用于相同的SQL语句

MyBatis的执行过程是怎样的?

对于MyBatis框架,我们如果想要执行sql语句,一定是要先去建立一个SqlSession,我们先从这个SqlSession看起。

SqlSession

SqlSession是为Mybatis提供服务的主要Java接口,通过此接口可以执行命令,获得映射和管理事务。SqlSession并不是线程共享的,在一个SqlSession中可以执行多个sql,但多个SqlSession之间并不共享。

SqlSession为我们提供了各种各样的api,光是查询就提供了14个之多,这里SqlSession就采用了门面模式,为用户提供各种各样的api方便使用。

SqlSession的api可分为两类,基础api(增删改查),辅助api(提交、关闭会话),SqlSession的作用就像一个去饭店吃饭时的菜单,他并不负责做菜,他只是把所有的菜品都罗列出来让你选择,那么选好菜了下一步要干什么呢,往下看。

Executor

来到Executor中,相较于SqlSession并没有提供增和删,而是将其归类于修改,也就是说只有改、查。基础功能:改、查、缓存维护、事务维护,辅助API:提交、关闭执行器、批处理刷新(在执行批处理后一定要调用此方法,批处理才会生效,类似于事务的commit)。

Executor的种类

Executor可以分为下面几种类型,分别为SimpleExecutor简单执行器,ReuseExecutor可重用执行器,BatchExecutor批处理执行器,BaseExecutor,CachingExecutor二级缓存执行器。

下面分别演示一下三种执行器的区别

  1. SimpleExecutor

    //1.SimpleExecutor简单执行器
    //select * from user where id=4 查询用户表中id为4的用户信息
    @Test
    public void simpleTest() throws SQLException {
        SimpleExecutor executor = new SimpleExecutor(configuration,jdbcTransaction);
        List<Object> list = executor.doQuery(ms, 4, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(4));
        executor.doQuery(ms, 4, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(4));
        System.out.println(list.get(0));
    }
    

    查询结果:

    ==> Preparing: SELECT * FROM user WHERE id=?
    > Parameters: 4(Integer)
    <
    Total: 1
    ==> Preparing: SELECT * FROM user WHERE id=?
    > Parameters: 4(Integer)
    <
    Total: 1

    可以看到,同一条查询语句参数也相同,SimpleExecutor执行了两次,这个和我们上面讲的jdbc中的statement有点相似,他不会去重用sql,只会一条一条的执行。那我们再来看看可以重用sql的ReuseExecutor吧

  2. ReuseExecutor

    @Test
    public void reuseTest() throws SQLException {
        ReuseExecutor executor = new ReuseExecutor(configuration,jdbcTransaction);
        List<Object> list = executor.doQuery(ms, 4, RowBounds.DEFAULT, ReuseExecutor.NO_RESULT_HANDLER, ms.getBoundSql(4));
        executor.doQuery(ms, 4, RowBounds.DEFAULT, ReuseExecutor.NO_RESULT_HANDLER, ms.getBoundSql(4));
        System.out.println(list.get(0));
    }
    

    查询结果:

    ==> Preparing: SELECT * FROM user WHERE id=?
    > Parameters: 4(Integer)
    <
    Total: 1
    > Parameters: 4(Integer)
    <
    Total: 1

    这里就不一样了,通过这个ReuseExecutor执行器,我们可以发现,他只执行了一条sql,设置了两次参数。那他是怎么做到的呢,我们点进去看一下源码就知道了(为了便于阅读删除了部分方法)。

    public class ReuseExecutor extends BaseExecutor {
    	//内部维护了一个HashMap容器存放执行的sql语句
      private final Map<String, Statement> statementMap = new HashMap<String, Statement>();
    
      public ReuseExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
      }
    	private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        //执行器前检查是否存在sql
        if (hasStatementFor(sql)) {
          stmt = getStatement(sql);
          applyTransactionTimeout(stmt);
        } else {
          //如果不存在,则创建出sql对应的statement,并放入map容器
          Connection connection = getConnection(statementLog);
          stmt = handler.prepare(connection, transaction.getTimeout());
          putStatement(sql, stmt);
        }
        handler.parameterize(stmt);
        return stmt;
      }
    	//检查map容器中是否存在要执行的sql
      private boolean hasStatementFor(String sql) {
        try {
          return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
        } catch (SQLException e) {
          return false;
        }
      }
    
      private Statement getStatement(String s) {
        return statementMap.get(s);
      }
    	//将sql语句存入map容器
      private void putStatement(String sql, Statement stmt) {
        statementMap.put(sql, stmt);
      }
    
    }
    

    怎么样,是不是很简单。

  3. BatchExecutor

    @Test
    public void batchTest() throws SQLException {
        BatchExecutor executor = new BatchExecutor(configuration,jdbcTransaction);
        MappedStatement mappedStatement = configuration.getMappedStatement("com.lxy.dao.UserDao.update");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("name","张三");
        map.put("id",4);
        executor.doUpdate(mappedStatement,map);
        executor.doUpdate(mappedStatement,map);
        executor.flushStatements(false);
    }
    

    执行结果:

    ==> Preparing: update user set name = ? where id = ?
    ==> Parameters: 张三(String), 4(Integer)
    ==> Parameters: 张三(String), 4(Integer)

    这里看起来是不是和ReuseExecutor很像,感觉他们好像就是一样的啊,有什么区别吗?是有区别的,ReuseExecutor是设置一次参数执行一次sql。而BatchExecutor如果设置多个参数,那么最新的参数会覆盖旧的参数,例如下面的例子。

    @Test
    public void batchTest() throws SQLException {
        BatchExecutor executor = new BatchExecutor(configuration,jdbcTransaction);
        MappedStatement mappedStatement = configuration.getMappedStatement("com.lxy.dao.UserDao.update");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("name","张三");
        map.put("id",4);
        Map<String, Object> map1 = new HashMap<String, Object>();
        map1.put("name","张三2");
        map1.put("id",4);
        executor.doUpdate(mappedStatement,map);
        executor.doUpdate(mappedStatement,map1);
        executor.flushStatements(false);
    }
    

    我们稍作修改,多了一个map1,将map1中的name更改为张三2,执行结果如下:

    ==> Preparing: update user set name = ? where id = ?
    ==> Parameters: 张三(String), 4(Integer)
    ==> Parameters: 张三2(String), 4(Integer)

    数据库结果:

    注意:批处理执行器对查询语句是无效的,如果使用此执行器进行查询,效果和SimpleExecutor一样。

    现在我们知道了批处理也会重用sql语句,但是下面演示的情况可就不一样了,在两次修改中穿插两次添加,最后的sql语句会有几条呢?

    @Test
    public void batchTest(){
        SqlSession session = factory.openSession(ExecutorType.BATCH,true);
        UserDao mapper = session.getMapper(UserDao.class);
        mapper.update("测试1",2);
        mapper.insert("批处理测试");
        mapper.insert("批处理测试");
        mapper.update("测试1",4);
        List<BatchResult> batchResults = session.flushStatements();
    }
    

    结果:

    这里怎么不一样了,不是说批处理执行器也会重用sql语句吗,应该是两条啊,怎么变成三条了?

    其实想让批处理执行器重用sql语句是有一定条件的,首先,调用的方法的sql和mappedStatement必须相同,其次,调用的顺序必须相邻,上面的演示并没有遵守第二个要求,所以对于修改语句,批处理执行器没有重用sql。

  4. BaseExecutor

    实现了Executor接口,是上面三个执行器的父类,它负责的是缓存的维护和事务管理。

  5. CachingExecutor二级缓存执行器

    这里体现的装饰器模式,装饰的是BaseExecutor,CachingExecutor内维护一个delegate,也就是BaseExecutor,通过执行delegate的方法前后进行一定的处理达到加强的效果。

热门文章

暂无图片
编程学习 ·

spring Security

spring Security简单介绍:Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架,它是用于保护基于Spring的应用程序的实际标准。Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring Security的真正强大之处在…
暂无图片
编程学习 ·

SSM整合小案例

SSM整合 数据库部分(Oracle)创建表 CREATE TABLE product( id varchar2(32) default SYS_GUID() PRIMARY KEY, productNum VARCHAR2(50) NOT NULL, productName VARCHAR2(50), cityName VARCHAR2(50), DepartureTime timestamp, productPrice Number, productDesc VARCHAR2(500…
暂无图片
编程学习 ·

php发送stmp邮件类 给有需要的人

<?php/*** email smtp (support php7)** Modified by: Reson 2019/06** More: http://www.wan0551.com**/class Smtp {/* Public Variables */public $smtp_port;public $time_out;public $host_name;public $log_file;public $relay_host;public $debug;public $auth;pu…
暂无图片
编程学习 ·

Mysql 5.7实现存在则更新,不存在则新增

需求:如果表中存在某行,那么更新即可;不存在某行,那么就新增一条。通常是将主键索引或唯一索引作为判断条件。思路:可以使用Mysql的INSERT ... ON DUPLICATE KEY UPDATE或REPLACE或UPDATE实现。如果希望一条语句实现,可以考虑前两种实现创建一张表,表中包含自增Id和唯一…
暂无图片
编程学习 ·

AppcompaActivity 相对于 Activity 的区别

1、AppcompaActivity 带 ActionBar 标题栏,Activity 则不带。参考文档显示ActionBarActivity已经过时,使用AppCompatActivity代替。2、theme 主题只能用 android:theme=”@style/AppTheme (appTheme主题或者其子类),不能使用 android:style。
暂无图片
编程学习 ·

浅析深究什么是SOA

1. 背景 IT行业就是术语和缩写流行的行业,各大厂商都喜欢隔三差五地推出一些新概念。为了不落人后,大家都喜欢争先恐后地跟进。有深入研究、务实研发的供应商,能够将概念落地,不断推出创新的产品和服务,赢得竞争优势。但“贴标签”的也大有人在,而且趋势是越贴越多,跟风…
暂无图片
编程学习 ·

redis基础知识汇总

redis基础知识汇总一、redis 基础知识1. 什么是Redis?2. redis的优缺点?3. redis比memcached的优势在哪里?以及两者的区别?4. redis的持久化策略?5. Redis过期键的删除策略6. 数据淘汰策略7. redis的事务?8、redis 事件?9、redis 集群redis哨兵数据分片主从配置二、Redi…
暂无图片
编程学习 ·

前端React实现fetch取消、中止请求

场景: 项目开发过程中有时会遇到这种情况:两次查询请求相隔时间很短时,由于接口异步,第一次请求可能会覆盖第二次请求返回数据,所以需要在第二次请求前先将第一次请求中止,话不多说,实现如下: 关于axios取消请求网上有很多,可自信百度,本文主要针对于fetch请求,由于…
暂无图片
编程学习 ·

Python代码

hello world!!! import numpy as npfrom sklearn import linear_modelfrom mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as pltxx, yy = np.meshgrid(np.linspace(0,10,10), np.linspace(0,100,10)) zz = 1.0 * xx + 3.5 * yy + np.random.randint(0,100,…
暂无图片
编程学习 ·

Java数据类型

数据类型 Java属于一种强类型语言 什么是强类型语言? 即要求变量的使用需要严格符合规定,要求所有变量都必须先定义后再使用,若不按规定就会报错! Java的数据类型分为两类 基本类型(primitive type) Java语言提供了八种基本类型:六种数字类型(四个整数型,两个浮点型),…
暂无图片
编程学习 ·

海思NNIE开发系列文章--转载

https://blog.csdn.net/zh8706/article/details/94554337海思NNIE开发系列文章:海思NNIE开发(一):海思Hi3559AV100/Hi3519AV100 NNIE深度学习模块开发与调试记录海思NNIE开发(二):FasterRCNN在海思NNIE平台上的执行流程(一)海思NNIE开发(三):FasterRCNN在海思NNIE平…
暂无图片
编程学习 ·

C语言指针笔记

C语言指针 一.地址与指针变量 程序在执行过程中需要有内存来存储需要用到的数据和程序代码,它们都占据一些内存单元,地址是这些内存单元的编号,同时包括它所指向的数据的类型信息。因此,可以把地址形象化地称为"指针"。 但不要把地址和指针混为一个概念,地址是数…
暂无图片
编程学习 ·

游戏开发中的人工智能

前言 今天非常开心,观看cocos官方直播居然在几千人中中奖,可以买彩票了。 言归正传,所谓的人工智能,也就是大家常说的AI(Artificial Intelligence)。一说到AI可能就会让人觉得比较深奥,其实也就是非玩家角色思考和行为的综合。比如,在什么样的条件下,触发什么样的行为…
暂无图片
编程学习 ·

C++ builder listview 自绘条形图

菜鸟的学习笔记,如果对你有用最好。高手请无视。ListView 要开 Doublebuffervoid __fastcall TForm1::ListView1CustomDrawSubItem(TCustomListView *Sender, TListItem *Item,int SubItem, TCustomDrawState State, bool &DefaultDraw) { DefaultDraw=true; int lef=0; T…
暂无图片
编程学习 ·

VMware下安装CentOS7

VMware下安装CentOS7和安装CentOS6.5是一样的,不同的就是在配置硬件时,根据自己电脑情况选择合理的需求。在这里大家可以参考我之前写的一篇博文:VMware下安装CentOS6.5Centos官方地址:https://www.centos.org/ 这个是最新版本 下载地址:https://www.centos.org/download/…
暂无图片
编程学习 ·

HTTP协议

HTTP入门1.为什么要学HTTP?我们绝大多数的web应用都是基于http来进行开发的,我们对web的操作都是通过http协议进行传输数据的;简单来说,http协议就是客户端和服务器交互的 一种通讯格式。Http的诞生主要是为了能够文档之间相互关联,形成超文本可以互相传阅,可以说http就是…
暂无图片
编程学习 ·

web页面移动端滚动失效问题

项目背景:Angular7 + echarts 4.8.0 开发的web项目。有一个全是由各种图表组成的页面,在android10系统的手机上访问页面时,发现滑动界面不能正常的向下滚动,只有滑动非图表区域的地方,才能滚动。但是在pc端和ios系统的手机,以及android5.1系统的手机上。页面滚动时没有问题…