Mybatis原理——执行原理详解

总结于B站鲁班大叔视频:https://www.bilibili.com/video/BV1Tp4y1X7FM?p=13&spm_id_from=pageDriver

概述

JDBC的执行流程可以大致分为:

  1. 获得连接
  2. 预编译sql
  3. 设置参数
  4. 执行sql

Mybatis执行原理大致分为:

  1. 动态代理MapperProxy
  2. sql会话Sqlsession
  3. 执行器Executor
  4. JDBC处理器StatementHandler

 

图中JDBC圈起来的部分就对应了sql具体的执行过程,属于Mybatis执行器范围内。本文主要针对sql会话与执行器来展开讨论。

Mybatis的执行过程

Mybatis使用门面模式提供了统一的门面接口API:

  • 基本的API:增、删、改、查等
  • 辅助的API:提交、关闭会话等

这些统一的API就是Sqlsession提供的,具体的API实现交由执行器来完成

Executor的实现

  • SimpleExecutor(默认实现)
    • 其每次执行sql,无论sql是否一样,都会进行sql的预处理
  • ReuseExecutor(可复用MappedStatement,MappedStatement包装的是sql信息)
    • 如果多次执行的sql一致,只会进行一次sql预处理
    • sql的重用,之和sql有关,即sql和参数一致,就可以重用,这个很重要
  • BatchExecutor(批处理)
    • 只针对增、删、改操作,也就是修改操作。如果是查询语句,则和SimpleExecutor没有区别。
    • 在批处理的情况下,对相同的sql,只会预处理一次,多次设置sql的参数值。
    • 必须手动调用BatchExecutor.doFlushStatement()方法提交事务。
  • BaseExecutor(执行器抽象类)
    • 实现SimpleExecutor、BatchExecutor、ReuseExecutor三者的重复操作:一级缓存、获取连接等
    • SimpleExecutor、BatchExecutor、ReuseExecutor继承BaseExecutor,BaseExecutor实现Executor
    • 定义query和doUpdate方法供SimpleExecutor、BatchExecutor、ReuseExecutor使用。

一级缓存

由于一级缓存、获取连接的实现在BaseExecutor中,单独使用SimpleExecutor、BatchExecutor、ReuseExecutor就无法得到BaseExecutor的支持

  • BaseExecutor中query方法会创建缓存的key,并调用其重载的方法query方法,通过localcache(key)去获取一级缓存,如果没有缓存,就会去调用doQuery方法,这个【doQuery】方法就是子类(SimpleExecutor、BatchExecutor、ReuseExecutor)中实现的数据库操作方法

二级缓存

  • CachingExecutor
    • 实现Executor接口
    • 只专注实现二级缓存的逻辑
    • CachingExecutor二级缓存的逻辑执行完成之后,将会把业务交由下一个执行器处理。下一个执行器(SimpleExecutor或BatchExecutor或ReuseExecutor)由构造方法指定。装饰者模式。

需要注意的是:

  • 一级缓存的数据是开始执行的时候就生成了,二级缓存的数据是提交执行过后才会生成
  • 之后的查询会先访问二级缓存,再访问一级缓存。

缓存命中场景

一级缓存

一级缓存是key-value形式的,实质底层就是一个HashMap。作用范围是sqlsession,当数据库会话结束之后,随之消亡

命中条件

  • sql和参数相同。
  • 相同的statementID(需要调用相同mapper中的查询方法相同)
  • 同一个sqlsession(这就是为什么一级缓存也叫会话级缓存)
  • 如果是分页查询,使用的RowBounds也要相同(也就是查询起始行、查询数据大小都要相同)

影响命中一级缓存的设置

  • 手动清空sqlsession.clearCache()
  • Mapper层(DAO层)查询方法使用了@Options(fluashCache = Opions.FlushCachePolicy.TRUE)
    • 该参数设置在每次查询后都会清空一级缓存
  • 执行了update操作
  • rollback也会清空相应的缓存

Spring集成mybatis一级缓存失效问题

spring集成Mybatis之后,不满足同一个sqlsession,导致一级缓存失效。在没有配置事务的情况下,每次执行sql都会构造一个新的会话。是由于srping的动态代理导致的。

解决办法就是将sql执行放在同一个事务中,就会使用同一个sqlsession,同一个sqlsession的情况下,一级缓存就不会失效

二级缓存

为什么已经有了一级缓存,还需要二级缓存呢?

二级缓存也称作是应用级缓存,与一级缓存不同的是它的作用范围是整个应用,而且可以跨线程使用。所以二级缓存有更高的命中率,适合缓存一些修改较少的数据

  • 二级缓存可以使用内存和磁盘进行存储。
  • 溢出淘汰:在有限的内存中缓存数据,始终会面临缓存承载满的情况,就会存在缓存淘汰机制
    • FIFO,先进先出的淘汰算法。
    • LRU,最近最少使用的淘汰机制。
  • 缓存设置过期时间,会进行过期清理。
  • 线程安全
  • 命中率统计
  • 序列化

Mybatis二级缓存设计、

接口定义:

实现类

Mybatis使用装饰器+责任链模式构建了完整的二级缓存结构:

  • SynchronizedCache:线程同步
  • LoggingCache:记录命中率
  • LruCache:防止溢出
  • ScheduledCache:过期清理
  • BlockingCache:防止内存穿透
  • PerpetualCache:内存存储

调用Cache实现类的任何方法都会沿着上面的xxxCache完成装饰,并执行

二级缓存命中条件

  • 必须提交了事务
    • 即使是设置了autocommite也不能命中缓存,除非是手动调用了commite方法,或者是关闭当前的事务
  • sql和参数相同。
  • 如果是分页查询,使用的RowBounds也要相同(也就是查询起始行、查询数据大小都要相同)

影响二级缓存命中的设置

  • 缓存的开关userCache = true。
  • Mapper层(DAO层)查询方法使用了@Options(fluashCache = Opions.FlushCachePolicy.TRUE)
    • 该参数设置在每次查询后都会清空一级缓存
  • 声明缓存空间
    • <cache><cache/>或者@CacheNamespace
    • 同时必须引用缓存空间:<cache-ref>或@CacehNamespaceRef

为什么要提交之后才能命中二级缓存?

因为二级缓存是跨线程使用的

例如sqlsessionA和sqlsessionB去查询同样的数据

  • A先修改了数据,再对该数据进行查询
  • B去查询该数据将结果填充进入二级缓存
  • A去查询该数据的时候,B进行的回滚
  • A这时候如果从二级缓存读取数据,就会产生脏读

所有执行结果在commit之前,都会放入事务缓存管理器的暂存区,只有被commit之后才会放入缓存区,这里的缓存区指的就是装饰器+责任链模式构建了完整的二级缓存结构。

StatementHandler

JDBC处理器,基于JDBC构建的Statement并设置参数,然后执行sql,没调用会话当中的一次sql,就有与之相对应的且唯一的Statement实例。

主要功能

  • 声明(创建)Statement
    • prepare方法基于Connection创建Statement
  • 设置参数
  • 查询
  • 修改

主要是实现:

  • BaseStatementHandler(从子类处理器中抽象出公共的处理逻辑)
  • SimpleStatementHandler(简单处理器)
  • PreparedStatementHandler(预处理器)
    • 大多数情况下都是使用该处理器,预处理性能更高,会进行参数转义,防止sql注入
  • CallableStatementHandler(存储过程处理器)

PreparedStatementHandler执行流程

这个过程包括从 执行器 -> StatementHandler -> 参数处理 -> 结果集处理

  • 执行器
  • StatementHandler
    • 预编译
    • 设置参数
    • 执行sql
    • 结果集映射成Java bean

声明或者是创建Statement

执行查询的开始代码,是在具体Executor的实现类中的doQuery方法:

  @Override
  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();
      //创建Statement
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //创建Statement实例
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
  
 //RoutingStatementHandler构造方法根据Statement类型创建具体的Statement
 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

创建完Statement之后,回到doQuery方法,调用prepareStatement()方法

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    //创建Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    //设置参数
    handler.parameterize(stmt);
    return stmt;
  }

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //instantiateStatement抽象方法由具体的子类实现(PreparedStatementHandler、SimpleStatementHandler、CallableStatementHandler)
      statement = instantiateStatement(connection);
      //设置超时时间
      setStatementTimeout(statement, transactionTimeout);
      //设置返回行数
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

回到prepareStatement方法,handler.parameterize(stmt);设置参数。parameterize方法是各个StatementHandler实现类的具体实现。最后回到doQuery方法通过具体的handler的query方法执行真正的sql操作




Mybatis的运行原理

获取SqlSessionFactory对象

  1. 创建SqlSessionFactory实际上就是加载配置信息,包括Mybatis的配置文件以及加载Mapper文件。
  2. 将Mapper文件中的sql解析为MappedStatement(一个MappedStatement对应一个mapper文件中的sql) 以及 配置信息封装为Configuration。
  3. Configuration注入DefaultSqlSessionFactory返回。

OpenSession

DefaultSqlSessionFactory的openSession

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

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

获取查询的代理对象

openSession执行之后返回了DefaultSqlSession,DefaultSqlSession用来获取查询的代理对象。其实DefaultSqlSession已经可以根据之前加载的配置信息以及MappeStatement进行查询了。

DefaultSqlSession的getMapper

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

Configuration

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

MapperRegistry

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //根据Mapper的代理工厂构建Mapper的代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

MapperProxyFactory

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

  protected T newInstance(MapperProxy<T> mapperProxy) {
    //JDK动态代理
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

最终返回的Mapper

执行sql

执行流程就是执行器Executor的原理的,中间穿插了一级缓存和二级缓存的逻辑。

  1. 代理对象包含了DefaultSqlSession,实际执行查询也是通过DefaultSqlSession
  2. DefaultSqlSession的创建中也包含了具体的执行器,通过执行器的doQuery进行实际查询
  3. 具体的Executor创建其对应的StatementHandler,进行参数的设置,预编译(ParameterHandler),执行查询(StatementHandler),返回结果处理(ResultSetHandler)

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值