mybatis执行sql流程和缓存超级详解

简介: mybatis执行sql流程和缓存超级详解

一,mybatis的数据加载流程

1,首先会通过这个SqlSessionFactoryBuilder 解析各个配置文件

// 通过加载配置文件流构建一个SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

2,通过这个 XMLConfigBuilder 的一个构造器,将所有的xml配置文件和xml的mapper映射文件解析出来,然后封装在一个 configuration 的一个对象里面,最后会将这个 configuration 对象加入到这个SqlSessionFactory里面

XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);

3,执行这个sqlSession的数据源,从这个sqlSessionFactory中获取这个 SqlSession

// 数据源 执行器  DefaultSqlSession
SqlSession session = sqlSessionFactory.openSession();

4,sqlSession创建成功之后,就可以执行增删改查的操作了

try{
    User user = (User)session.selectOne("com.zhs.study.UserMapper.selectById", 1);
    System.out.pringln(user.getUserName);
}

二,SqlSession

主要通过门面模式,来实现上应用层和mybatis底层交互的一个桥梁,通过这个sqlsession就可以进行数据库的一些操作。主要会提供一些api接口,如增删改查,提交关闭,回滚等。这个sqlsession类如下

public interface SqlSession extends Closeable {
    <T> T selectOne(String statement);
  <E> List<E> selectList(String statement);
  void select();
  int update(String statement, Object parameter);
  int delete(String statement);
  void commit();
  void rollback();
}

在这个过程中,会通过这个openSession方法,来获取一个 Executor 执行器类

SqlSession session = sqlSessionFactory.openSession();

三,Executor

在这个接口中,只封装了改和查两种方法,这个增删改都是在这个改的方法里面实现的。然后其他的事务,缓存等,也是通过这个 Executor 来具体实现。

//修改
int update(MappedStatement ms, Object parameter) throws SQLException;
//查询
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
//提交事务
void commit(boolean required) throws SQLException;
//回滚事务
void rollback(boolean required) throws SQLException;
//清除缓存
void clearLocalCache();
// 延迟加载
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
//获取一个事务
Transaction getTransaction();
// 关闭事务
void close(boolean forceRollback);

3.1,Executor执行器具体实现类

simpleExecutor :每执行一次update或select,都会创建这个预处理器prepareStatement


reuseExecutor :会重用这个prepareStatement,通过这个缓存实现


batchExecutor :批量处理,如可以批量处理select,update


BaseExecutor : 作为上面三种执行器的一级缓存。在sqlsession中,如果执行了相同的一条sql语句,那么就可以触发这个一级缓存


CachingExecutor:作为上面三种执行器的二级缓存


3.2,Executor源码分析

1,在这个openSession的方法里面,会有一个 openSessionFromDataSource 方法

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

2,接下来主要查看这个 openSessionFromDataSource 方法,里面除了获取环境变量,事务工厂之外,还会创建一个 Executor 的执行器

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    //创建一个Executor执行器
    final Executor executor = configuration.newExecutor(tx, execType);
}

3,接下来查看这个创建Executor的newExecutor方法,主要是会创建三个Executor,分别是:SIMPLE,REUSE,BATCH。最后会去判断一下是否开启二级缓存,如果外面开启二级缓存,则会使用一个装饰者模式,对这个类进行一个类的一个包装

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //外部没有定义则使用这个默认的 SIMPLEExecutor
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    /**
     * 判断执行器的类型
     * 批量的执行器
     */
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      //可重复使用的执行器
      executor = new ReuseExecutor(this, transaction);
    } else {
      //简单的sql执行器对象
      executor = new SimpleExecutor(this, transaction);
    }
    //判断mybatis的全局配置文件是否开启缓存
    if (cacheEnabled) {
      //把当前的简单的执行器包装成一个CachingExecutor
      executor = new CachingExecutor(executor);
    }
    /**
     * TODO:调用所有的拦截器对象plugin方法
     */
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

四,执行增删改查命令以及缓存使用

1,在获取到这个sqlsseion之后,就可以执行这个增删改查的命令了

User user = (User)session.selectOne("com.zhs.study.UserMapper.selectById", 1);

2,接下来执行这个sql语句,如这个selectOne方法

@Override
public <T> T selectOne(String statement, Object parameter) {
    //调用 selectList 方法
  List<T> list = this.selectList(statement, parameter);    
}

3,然后在这个 selectList 方法里面,会调用这个Executor类里面的方法。因此这个具体操作这个mybatis底层的,是这个Executor类。

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    //获取对应的增删改查的结点
    MappedStatement ms = configuration.getMappedStatement(statement);
    //进入二级缓存
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}

4,接下来会先进入这个二级缓存 CachingExecutor 类的这个query方法

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //通过参数解析sql信息
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //创建缓存的key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

5,再次进入这个二级缓存里面的query方法。

首先会判断是否开启二级缓存,如果开启了二级缓存,那么他会先进入一个临时的事务缓存,如果在插入操作出现异常回滚,那么就不会进入下面的二级缓存的插入操作;没有异常的话,在提交的时候才会真正的保存在二级缓存。

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //判断我们我们的mapper中是否开启了二级缓存<cache></cache>
    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); 
          }
          return list;
        }
    }
    //没有整合二级缓存,直接去查询
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

6,如果开启二级缓存,那么就会进入这个一级缓存BaseExecutor类里面的这个query方法。首先会从这个一级缓存里面获取数据,如果一级缓存数据为空,那么就从数据库中获取数据。

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    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);
    }
}

7,从数据库查询的逻辑主要通过这个 queryFromDatabase 的方法实现。在将数据查完之后,会将数据加入到一级缓存里面。只要在这个sqlSession没有关闭,那么在这段时间的这条查询语句的结果是可以直接在这个一级缓存里面去拿。

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
        //查询
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
        //删除过期缓存
    localCache.removeObject(key);
  }
    //一级缓存中加入缓存
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

8,接下来主要查看这个SimpleExecutor里面的doQuery方法,里面主要会获取一个StatementHandler的一个对象,这个对象主要是用来获取connection连接,获取preStatement,参数映射,处理结果集等

@Override
public <E> List<E> doQuery(){
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
        //创建这个StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        //prepareStatement
        stmt = prepareStatement(handler, ms.getStatementLog());
        //prepareStatement方法查询
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

在这个newStatementHandler方法里面,会通过这个来StatementHandler进行参数映射和处理结果集

public StatementHandler newStatementHandler(...) {
    //主要用来进行参数映射和处理结果集
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

在这个预处理的底层实现如下

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

9,在8里面的这个query(stmt, resultHandler)方法,就是生成一个PreparedStatement进行一个预处理

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

10,然后就会调用这个execute方法执行sql语句,就是会将这个sql语句执行的结果,打包封装在这个wrappedStmt里面,最后将执行结果返回

public boolean execute() throws SQLException {
    try {
        if (this.wrappedStmt != null) {
            return ((PreparedStatement)this.wrappedStmt).execute();
        } else {
        }
    } catch (SQLException var2) {
        this.checkAndFireConnectionError(var2);
        return false;
    }
}

五,mybatis的动态sql解析

1,在数据查询的过程中,会调用mybatis的CachingExecutor 二级缓存的类,里面有一个query方法,然后里面的这个BoundSql就是用来解析sql的

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //开始解析动态sql
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

2,通过这个getBoundSql来进行具体的动态sql

public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
}

3,然后进入这个DynamicSqlSource类里面的getBoundSql方法,

@Override
public BoundSql getBoundSql(Object parameterObject) {
    //获取上下文全部的node结点
  DynamicContext context = new DynamicContext(configuration, parameterObject);
    //循环解析这些node结点
  rootSqlNode.apply(context);
  SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
  Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
}

4,通过这个apply方法,通过这个责任链模式,来解析全部的这个node结点。这些动态标签都会进行解析

六,总结

6.1,mybatis执行流程


1,首先会通过这个SqlSessionFactoryBuilder类来解析全部的环境变量,xml配置文件和xml的mapper映射文件


2,会将解析出来的信息封装在一个Configuration的一个配置类里面,然后会通过这个配置类会创建一个SqlSessionFactory的一个对象,这个对象里面会有这个配置类的所有信息


3,通过这个SqlSessionFactory的openSession方法来创建这个SqlSession,这个sqlsession只是一个门面模式,不会具体的操作sql语句,相当于应用层和mybatis底层交互的一个桥梁


4,sqlSession最终会将这个语句交给Executor的执行器处理,这个处理器可以执行增删改查命令的操作


5,在执行完命令之后,Executor执行器会创建一个StatementHandler的一个对象


6,StatementHandler会创建一个ParameterHandler对象和一个ResultSetHandler对象,ParameterHandler主要是给sql设置参数,ResultSetHandler对象主要是返回结果集


7,最后通过这个Statement的子接口PreparedStatement,调用里面的execute方法来获取这个这个执行的结果


6.2,mybatis一二级缓存

1,一级缓存默认开启,并且一级缓存的作用域是单个sqlsession。一级缓存在执行增删改的时候,一级缓存就会失效,并且清空。在一条查询的sql语句中,只要这个会话时间不过期,那么在这个一级缓存里面一直有效,


2,二级缓存需要在配置文件中手动开启,二级缓存作用域是这个sqlSessionFactory创建的的所有sqlsession都能被共享。


3,在查询一条sql语句时,如果二级缓存打开,那么会先去查这个二级缓存,如果二级缓存没有命中,则会查询这个数据库,查完数据库之后会将这个数据增加到二级缓存里面;在插入数据时会先加入到一个临时的事务缓存里面,如果出现这个插入回滚的现象,那么会直接将这个临时的事务缓存删除,从而不进入里面的二级缓存


4,在查询一条sql语句时,如果二级缓存没有打开,那么会直接查一级缓存,如果在一段session会话没有关闭,那么查这个一级缓存可以命中,如果一级缓存没有命中,那么就会查数据库,查完数据库之后会将这个数据增加到一级缓存里面


相关文章
|
1天前
|
SQL Java 数据库连接
【JavaEE】懒人的福音-MyBatis框架—复杂的操作-动态SQL(下)
【JavaEE】懒人的福音-MyBatis框架—复杂的操作-动态
5 0
|
1天前
|
SQL Java 数据库连接
【JavaEE】懒人的福音-MyBatis框架—复杂的操作-动态SQL(上)
【JavaEE】懒人的福音-MyBatis框架—复杂的操作-动态SQL
3 0
|
1天前
|
SQL Java 关系型数据库
Mybatis多表关联查询与动态SQL(下)
Mybatis多表关联查询与动态SQL
10 0
|
1天前
|
SQL Java 数据库连接
Mybatis多表关联查询与动态SQL(上)
Mybatis多表关联查询与动态SQL
9 0
|
1天前
|
SQL Java 数据库连接
mybatis动态sql
mybatis动态sql
|
1天前
|
SQL 算法
基于若依的ruoyi-nbcio流程管理系统修改代码生成的sql菜单id修改成递增id(谨慎修改,大并发分布式有弊端)
基于若依的ruoyi-nbcio流程管理系统修改代码生成的sql菜单id修改成递增id(谨慎修改,大并发分布式有弊端)
13 1
|
1天前
|
SQL
flowable的流程任务统计sql(续)
flowable的流程任务统计sql(续)
|
1天前
|
SQL
flowable的流程任务统计sql
flowable的流程任务统计sql
|
1天前
|
SQL Java 数据库连接
MyBatis #与$的区别以及动态SQL
MyBatis #与$的区别以及动态SQL
13 0
|
1天前
|
消息中间件 缓存 NoSQL
Redis经典问题:缓存雪崩
本文介绍了Redis缓存雪崩问题及其解决方案。缓存雪崩是指大量缓存同一时间失效,导致请求涌入数据库,可能造成系统崩溃。解决方法包括:1) 使用Redis主从复制和哨兵机制提高高可用性;2) 结合本地ehcache缓存和Hystrix限流降级策略;3) 设置随机过期时间避免同一时刻大量缓存失效;4) 使用缓存标记策略,在标记失效时更新数据缓存;5) 实施多级缓存策略,如一级缓存失效时由二级缓存更新;6) 通过第三方插件如RocketMQ自动更新缓存。这些策略有助于保障系统的稳定运行。
204 1
http://www.vxiaotou.com