四、MyBatis终结篇(补充中...)

一、MyBatis核心特性

MyBatis核心特性

MyBatis和Hibernate 跟 DbUtils、 Spring JDBC 一样,都是对 JDBC 的一个封装,我们去看源码,最后一定会看到 Statement 和 ResultSet 这些对象。

1、 使用连接池对连接进行管理
2、 SQL 和代码分离,集中管理
3、 结果集映射
4、 参数映射和动态 SQL
5、 重复 SQL 的提取
6、 缓存管理
7、 插件机制

这么多的工具和不同的框架,在实际的项目里面应该怎么选择?
在一些业务比较简单的项目中,我们可以使用 Hibernate;如果需要更加灵活的 SQL,可以使用 MyBatis,对于底层的编码,或者性能要求非常高的场合,可以用 JDBC。
实际上在我们的项目中,MyBatis 和 Spring JDBC 是可以混合使用的

二、MyBatis架构分层与模块划分

image-20200630151344356

2.1 接口层

首先接口层是我们打交道最多的。核心对象是 SqlSession,它是上层应用和 MyBatis打交道的桥梁,SqlSession 上定义了非常多的对数据库的操作方法。接口层在接收到调用请求的时候,会调用核心处理层的相应模块来完成具体的数据库操作。

2.2 核心处理层

接下来是核心处理层。既然叫核心处理层,也就是跟数据库操作相关的动作都是在这一层完成的。
核心处理层主要做了这几件事:
(1)把接口中传入的参数解析并且映射成 JDBC 类型;
(2)解析 xml 文件中的 SQL 语句,包括插入参数,和动态 SQL 的生成;
(3)执行 SQL 语句;
(4)处理结果集,并映射成 Java 对象。
插件也属于核心层,这是由它的工作方式和拦截的对象决定的。

2.3 基础支持层

最后一个就是基础支持层。基础支持层主要是一些抽取出来的通用的功能(实现复用),用来支持核心处理层的功能。比如数据源、缓存、日志、xml 解析、反射、IO、事务等等这些功能。

三、MyBatis缓存

缓存目的就是提升查询的效率和减少数据库的压力。跟 Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成在第三方缓存的接口(比如缓存在Redis)。

3.1 类体系

​ MyBatis 跟缓存相关的类都在 cache 包里面,其中有一个 Cache 接口,只有一个默认的实现类 PerpetualCache,它是用 HashMap 实现的。

​ 除此之外,还有很多的装饰器,通过这些装饰器可以额外实现很多的功能:回收策略、日志记录、定时刷新等等。

image-20200630154258951

“装饰者模式(Decorator Pattern) 是指在不改变原有对象的基础之上, 将功能附加到对象上, 提供了比继承更有弹
性的替代方案(扩展原有对象的功能) 。 ” 无论怎么装饰,经过多少层装饰,最后使用的还是基本的实现类(默认PerpetualCache)
在这里插入图片描述

3.2 分类

所有的缓存实现类总体上可分为三类:基本缓存、淘汰算法缓存、装饰器缓存

缓存实现类 描述 作用 装饰条件
基本缓存 缓存基本实现类 默认是 PerpetualCache, 也可以自定义比如 RedisCache、 EhCache 等, 具备基本功能的缓存类
LruCache LRU 策略的缓存 当缓存到达上限时候, 删除最近最少使用的缓存 (Least Recently Use) eviction="LRU"(默 认)
FifoCache FIFO 策略的缓存 当缓存到达上限时候, 删除最先入队的缓存 eviction="FIFO"
SoftCache WeakCache 带清理策略的缓存 通过 JVM 的软引用和弱引用来实现缓存, 当 JVM 内存不足时, 会自动清理掉这些缓存, 基于 SoftReference 和 WeakReference eviction="SOFT" eviction="WEAK

3.3 一级缓存

3.3.1 介绍

​ 一级缓存也叫本地缓存,MyBatis 的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis 的一级缓存是默认开启的,不需要任何的配置。

3.3.2 持有缓存位置

​ 要在同一个会话里面共享一级缓存,这个对象肯定是在 SqlSession 里面创建的,作为 SqlSession 的一个属性 DefaultSqlSession 里面只有两个属性,Configuration 是全局的,所以缓存只可能放在 Executor 里面维护——SimpleExecutor/ReuseExecutor/BatchExecutor 的父类 BaseExecutor 的构造函数中持有了 PerpetualCache。

3.3.2 功能

​ 在同一个会话里面,多次执行相同的 SQL 语句,会直接从内存取到缓存的结果,不会再发送 SQL 到数据库。但是不同的会话里面,即使执行的 SQL 一模一样(通过一个Mapper 的同一个方法的相同参数调用),也不能使用到一级缓存。

3.3.2 不足

​ 一级缓存不能跨会话共享,不同的会话之间对于相同的数据可能有不一样的缓存。在有多个会话或者分布式环境下,会存在脏数据的问题。如果要解决这个问题,就要用到二级缓存。

3.4 二级缓存

3.4.1 介绍

​ 二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是 namespace 级别的,可以被多个 SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。

3.4.2 时机

​ 作为一个作用范围更广的缓存,工作在一级缓存之前,也就是只有取不到二级缓存的情况下才到一个会话中去取一级缓存

3.4.3 功能

​ 要跨会话共享的话,SqlSession 本身和它里面的 BaseExecutor 已经满足不了需求了,实际上 MyBatis 用了一个装饰器的类来维护,就是 CachingExecutor。如果启用了二级缓存,MyBatis 在创建 Executor 对象的时候会对 Executor 进行装饰。

在这里插入图片描述

​ CachingExecutor 对于查询请求,会判断二级缓存是否有缓存结果,如果有就直接返回,如果没有委派交给真正的查询器 Executor 实现类,比如 SimpleExecutor 来执行查询,再走到一级缓存的流程。最后会把结果缓存起来,并且返回给用户

3.4.4 使用

第一步:在 mybatis-config.xml 中配置了(可以不配置,默认是 true):

<setting name="cacheEnabled" value="true"/>

只要没有显式地设置 cacheEnabled=false,都会用 CachingExecutor 装饰基本的执行器。
第二步:在 Mapper.xml 中配置标签:

<!-- 声明这个 namespace 使用二级缓存 -->
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
    size="1024" <!--最多缓存对象个数, 默认 1024-->
    eviction="LRU" <!--回收策略-->
    flushInterval="120000" <!--自动刷新时间 ms, 未配置时只有调用时刷新-->
    readOnly="false"/> <!--默认是 false(安全) , 改为 true 可读写时, 对象必须支持序列化 -->

3.4.5 适用场景

​ 一级缓存默认是打开的,二级缓存需要配置才可以开启。那么我们必须思考一个问题,在什么情况下才有必要去开启二级缓存?
​ 1、在查询为主的应用中使用,比如历史交易、历史订单的查询。因为所有的增删改都会刷新二级缓存,导致二级缓存失效。否则缓存就失去了意义。
​ 2、如果多个 namespace 中有针对于同一个表的操作,比如 Blog 表,如果在一个namespace 中刷新了缓存,另一个 namespace 中没有刷新,就会出现读到脏数据的情况。所以,推荐在一个 Mapper 里面只操作单表的情况使用。

3.5 用第三方缓存存二级缓存

1.引入pom,maven:mybatis-redis 包

2.Mapper.xml 配置,type 使用 RedisCache:

3.普通Redis地址配置

最后二级缓存存在了Redis里面,具体不多说 比较少见。

四、源码:基础案例

4.2 基本代码(不融入spring)

因为 Spring 对 MyBatis 的一些操作进行的封装,我们不能直接看到它的本质,所以先看下不使用容器的时候

        //读取配置
        String resource = "mybatis/mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //创建SqlSession会话工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //创建会话
        SqlSession session = sqlSessionFactory.openSession();
        //执行SQL
        try {
            BookMarkDao mapper = session.getMapper(BookMarkDao.class);
            BookMarkQuery bookMarkQuery = new BookMarkQuery();
            bookMarkQuery.setUserId(new Long("123"));
            List<BookMarkDO> bookMarkList = mapper.find(bookMarkQuery);
            System.out.println(bookMarkList);
        } finally {
            session.close();
        }

看到了 MyBatis 里面的几个核心对象:
SqlSessionFactoryBuiler、 SqlSessionFactory、 SqlSession 和 Mapper 对象。 这几个核心对象在 MyBatis 的整个工作流程里面的不同环节发挥作用。 如果说我们不用容器,自己去管理这些对象的话,我们必须思考一个问题:什么时候创建和销毁这些对象?

从每个对象的作用的角度来理解一下,只有理解了它们是干什么的,才知道什么时候应该创建,什么时候应该销毁

4.2 四个核心类作用及生命周期

1)SqlSessionFactoryBuiler

首先是 SqlSessionFactoryBuiler。它是用来构建 SqlSessionFactory 的, 而
SqlSessionFactory 只需要一个,所以只要构建了这一个 SqlSessionFactory,它的使命
就完成了,也就没有存在的意义了。所以它的生命周期只存在于方法的局部。

2)SqlSessionFactory

SqlSessionFactory 是用来创建 SqlSession 的,每次应用程序访问数据库,都需要
创建一个会话。 因为我们一直有创建会话的需要,所以 SqlSessionFactory 应该存在于
应用的整个生命周期中(作用域是应用作用域)。创建 SqlSession 只需要一个实例来做
这件事就行了,否则会产生很多的混乱,和浪费资源。所以我们要采用单例模式。

3)SqlSession

SqlSession 是一个会话,因为它不是线程安全的,不能在线程间共享。 所以我们在
请求开始的时候创建一个 SqlSession 对象,在请求结束或者说方法执行完毕的时候要及
时关闭它(一次请求或者操作中)。

4)Mapper

Mapper(实际上是一个代理对象)是从 SqlSession 中获取的。

BookMarkDao mapper = session.getMapper(BookMarkDao.class);

它的作用是发送 SQL 来操作数据库的数据。它应该在一个 SqlSession 事务方法之
内。

5) 总结

对象 生命周期
SqlSessionFactoryBuiler 方法局部(method)
SqlSessionFactory(单例) 应用级别(application)
SqlSession 请求和操作(request/method)
Mapper 方法(method)

五、传统Mybatis核心步骤与疑问

第一步,我们通过建造者模式创建一个工厂类,配置文件的解析就是在这一步完成的,包括 mybatis-config.xml 和Mapper 适配器文件。

问题:解析的时候怎么解析的,做了什么,产生了什么对象,结果存放到了哪里。解析的结果决定着我们后面有什么对象可以使用,和到哪里去取。

第二步,通过 SqlSessionFactory 创建一个 SqlSession。
问题:SqlSession 是用来操作数据库的,返回了什么实现类,除了 SqlSession,还创建了什么对象,创建了什么环境?

第三步,获得一个 Mapper 对象。

问题:Mapper 是一个接口,没有实现类,是不能被实例化的,那获取到的这个Mapper 对象是什么对象?为什么要从 SqlSession 里面去获取?为什么传进去一个接口,然后还要用接口类型来接收?

第四步,调用接口方法。

问题:我们的接口没有创建实现类,为什么可以调用它的方法?那它调用的是什么方法?它又是根据什么找到我们要执行的 SQL 的?也就是接口方法怎么和 XML 映射器里面的 StatementID 关联起来的?此外,我们的方法参数是怎么转换成 SQL 参数的?获取到的结果集是怎么转换成对象的?

六、逐步分析

待分析的基础代码:

InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlogById(1);

6.1 解析配置创建SqlSessionFactory

核心代码:

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

new 了一个 SqlSessionFactoryBuilder,非常明显的建造者模式,它里面定义了很多个 build 方法的重载,最终返回的是一个 SqlSessionFactory 对象(单例模式)。可以点进去 build 方法:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      //。。。。	
    } finally {
	  //。。。。	
}

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

核心两句话:

1.创建XMLConfigBuilder,调用parse进行解析

2.运用解析出来的Configuration,创建DefaultSqlSessionFactory

6.1.1 XMLConfigBuilder

XMLConfigBuilder 是抽象类 BaseBuilder 的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类,比如:

XMLMapperBuilder:解析 Mapper 映射器
XMLStatementBuilder:解析增删改查标签

在这里插入图片描述

调用 XMLConfigBuilder的 parse()方法,它会返回一个 Configuration ,配置文件里面所有的信息都会放在 Configuration 里面 。查看parse方法:

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      //分步骤解析
      //issue #117 read properties first
      //1.properties
      propertiesElement(root.evalNode("properties"));
      //2.类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //3.插件
      pluginElement(root.evalNode("plugins"));
      //4.对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      //5.对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.设置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.环境
      environmentsElement(root.evalNode("environments"));
      //8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9.类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10.映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

依次解析这里面的节点:configuration以及下属properties、settings、typeAliases等等,全部设置到内部属性Configuration里,也说明了写mybatis配置文件的顺序不能错。

其中这些标签配置里看下重点的mappers是怎么被解析的,我们的SQL、mapper是怎么解析的。(其它都是配置加载各种分支非重点)

6.1.2 mappers标签解析

主要是在上面代码:

XMLConfigBuilder#parse>parseConfiguration>mapperElement(root.evalNode("mappers"));里

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

标签的解析 ,里面有四类:

扫描类型 含义
resource 相对路径
url 绝对路径
package
class 单个接口

其中主要是两个,addMapper解析接口,XMLMapperBuilder 解析XML

addMapper

​ 无论是按 package 扫描,还是按接口扫描,最后都会调用到 MapperRegistry 的addMapper()方法。
MapperRegistry 里面维护的其实是一个 Map 容器,存储接口和代理工厂的映射关系。

​ 在 addMapper()方法跟进去 里面创建了一个 MapperAnnotationBuilder,又一个parse方法,都是对注解的解析,比如@Options(设置自增key),@SelectKey(返回自增key),@ResultMap 等等

​ 最后同样会解析成 MappedStatement 对象,也就是说在 XML 中配置,和使用注解配置,最后起到一样的效果

XMLMapperBuilder

XML,具体是XMLMapperBuilder.parse()方法,源码:

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

主要有两个方法:
1.configurationElement()—— 解 析 所 有 的 子 标 签 , 其 中buildStatementFromContext()最终获得 MappedStatement 对象。
2.bindMapperForNamespace()——把 namespace(接口类型)和工厂类绑定起来

6.1.3 小结

​ 完成了 config 配置文件、Mapper 文件、Mapper 接口上的注解的解析 得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。
​ 最后,返回了一个 DefaultSqlSessionFactory,里面持有了 Configuration 的实例。

6.2 会话创建过程openSession

​ 第二步,跟数据库的每一次连接,都需要创建一个会话,用openSession()方法来创建。

  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();
    }
  }

​ DefaultSqlSessionFactory —— openSessionFromDataSource()这个会话里面,需要包含一个 Executor 用来执行 SQL。

​ Executor 又要指定事务类型和执行器的类型。所以我们会先从 Configuration 里面拿到 Enviroment,Enviroment 里面就有事务工厂。

6.2.1 创建 Transaction

​ 如果配置的是 JDBC,则会使用 Connection 对象的 commit()、rollback()、close()管理事务。
​ 如果配置成 MANAGED,会把事务交给容器来管理,比如 JBOSS,Weblogic。(这里翻过车,SpringBoot+Resin不去配的话没得事物了,事物失效,Tomcat可以)

​ 因为我们跑的是本地main程序,如果配置成 MANAGE 不会有任何事务。
​ 如 果 是 Spring + MyBatis , 则 没 有 必 要 配 置 , 因 为 我 们 会 直 接 在applicationContext.xml 里面配置数据源和事务管理器,覆盖 MyBatis 的配置。

6.2.2 Executor类型

​ Executor 的基本类型有三种:SIMPLE、BATCH、REUSE,默认是 SIMPLE(settingsElement()读取默认值),他们都继承了抽象类 BaseExecutor。

​ 为什么要让抽象类实现接口,然后让具体实现类继承抽象类?(模板方法模式)

​ 定义一个算法的骨架, 并允许子类为一个或者多个步骤提供实现。模板方法使得子类可以在不改变算法结构的情况下, 重新定义算法的某些步骤。 ”

​ 三种类型的区别(通过 update()方法对比)?

​ SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。
​ ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map 内,供下一次使用。简言之,就是重复使用 Statement 对象。
​ BatchExecutor:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行executeBatch()批处理。与 JDBC 批处理相同。

​ 如果配置了 cacheEnabled=ture,会用装饰器模式对 executor 进行包装:new
CachingExecutor(executor)。

6.2.3 小结

​ 创建会话的过程,我们获得了一个 DefaultSqlSession,里面包含了一个Executor,它是 SQL 的执行者。

6.3 获取Mapper对象

​ 在解析 mapper 标签和 Mapper.xml 的时候已经把接口类型和类型对应的 MapperProxyFactory 放到了一个 Map 中。获取 Mapper 代理对象,实际上是从Map 中获取对应的工厂类后,调用MapperProxyFactory.newInstance() 方法创建对象

​ 即JDK 动态代理:

	return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{ mapperInterface }, mapperProxy);  

​ 普通JDK 动态代理代理,在实现了 InvocationHandler 的代理类里面,需要传入一个被代理对象的实现类

​ mybatis不需要实现类的原因:我们只需要根据接口类型+方法的名称,就可以找到Statement ID 了,而唯一要做的一件事情也是这件,所以不需要实现类

6.5 执行SQL

​ 获得 Mapper 对象的过程,实质上是获取了一个 MapperProxy 的代理对象。MapperProxy 中有 sqlSession、mapperInterface、methodCache。

​ 由于所有的 Mapper 都是 MapperProxy 代理对象,所以任意的方法都是执行MapperProxy 的 invoke()方法,debug进mapper(代理对象)的执行方法。

6.5.1 MapperProxy.invoke()

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //先从缓存里拿MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

这里可以看出Mapper方法的抽象是MapperMethod,先从methodCache缓存Map中取,没有则new一个。

然后执行MapperMethod的execute

6.5.2 mapperMethod.execute

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
        result = executeForCursor(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      //...
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException("...");
  }
  return result;
}

分类INSERT、UPDATE、DELETE、SELECT执行,以查询方法为例跟进,进select的executeForMany里

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

最终还是交给sqlSession,它的selectList去查询

6.5.3 DefaultSqlSession.selectList()

selectOne()最终也是调用了 selectList()

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

在 SelectList()中,我们先根据 command name(Statement ID)从 Configuration中拿到 MappedStatement,这个 ms 上面有我们在 xml 中配置的所有属性,包括 id、statementType、sqlSource、useCache、入参、出参等等。

在这里插入图片描述

​ 然后执行了 Executor 的 query()方法。 Executor 有三种基本类型,SIMPLE/REUSE/BATCH,还有一种包装类型,CachingExecutor。

​ 如果启用了二级缓存,就会先调用 CachingExecutor 的 query()方法,里面有缓存相关的操作,然后才是再调用基本类型的执行器,比如默认的 SimpleExecutor。

​ 此处惊奇的发现网上大量文章说要主动去开启二级缓存是错的!跟进源码Configuration属性boolean cacheEnabled = true;默认true,发现二级缓存默认开启,通过官方文档也证实了:
在这里插入图片描述

​ 执行executor.query实际上是CachingExecutor的query,公司Springboot项目也是

6.5.4 CachingExecutor#query

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

根据MappedStatement、参数、limit、sql创建缓存的key。

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

先从缓存取否则,执行delegate.query,这里就交给父类BaseExecutor执行query了

6.5.5 BaseExecutor.query()

  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

1)是否清空本地缓存

queryStack 用于记录查询栈,防止递归查询重复处理缓存。flushCache=true 的时候,会先清理本地缓存(一级缓存):clearLocalCache();

2)从一级缓存取
利用CacheKey。这个 CacheKey 就是缓存的 Key,localCache.getObject(key),从一级缓存取。如果没有缓存,会从数据库查询:queryFromDatabase()

3)从数据库查询
a)缓存
先在缓存localCache用个占位符占好。等之后执行查询后,移除占位符,放入数据。
b)查询
执行 Executor 的 doQuery();默认是 SimpleExecutor 的doQuery

6.5.6SimpleExecutor.doQuery()

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

(1) 创建 StatementHandler

这里会根据 MappedStatement 里面的 statementType 决定创建StatementHandler 的 类 型 。 默 认 是 PREPARED 。

1、STATEMENT:直接操作sql,不进行预编译,获取数据:$—Statement
2、PREPARED:预处理,参数,进行预编译,获取数据:#—–PreparedStatement:默认
3、CALLABLE:执行存储过程————CallableStatement

<update id="update5" statementType="PREPARED">
    update tb_car set xh=#{xh} where id=#{id}
</update>

StatementHandler 里面包含了处理参数的 ParameterHandler 和处理结果集的ResultSetHandler。

这两个对象都是在上面 new 的时候创建的 。这三个对象都是可以被插件拦截的四大对象(还有个Executor),所以在创建之后都要用拦截器进行包装的方法。

(2)预编译Statement,处理参数

用 new 出来的 StatementHandler 创建 Statement 对象——prepareStatement()方法对语句进行预编译,处理参数。

(3)执行 StatementHandler 的 query()方法

RoutingStatementHandler 的 query()方法。delegate 委派,最终由PreparedStatementHandler(上面statementType 决定的) 的 query()方法执行。

(4)PreparedStatement 的 execute()

public <E> List<E> query(Statement statement, ResultHandler resultHandler)  {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  return resultSetHandler.handleResultSets(ps);
}

前两是执行 JDBC 包中的 PreparedStatement ,原生SQL执行

在这里插入图片描述

MyBatis只要是最后的handleResultSets,处理结果;

(5)ResultSetHandler 处理结果集

ResultSetHandler 只有一个实现类:DefaultResultSetHandler。实现的步骤就是将Statement执行后的结果集,按照Mapper文件中配置的ResultType或ResultMap来封装成对应的对象,最后将封装的对象返回 。

通过不断while循环调用下一行结果,取匹配Map里的对象映射,对属性赋值

@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    // 第一个结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 获取 resultMap 
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // 判断 ResultMap 是否为空,空则抛异常
    validateResultMapsCount(rsw, resultMapCount);
    // 处理第一个结果集
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 将结果集映射为对应的 ResultMap 对象
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
        // 多个结果集
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }
posted @ 2020-07-04 11:37  词汇族  阅读(257)  评论(0编辑  收藏  举报