Mybatis源码分析-执行器

Mybatis四大组件之一:Executor执行器

一:Executor流程图以及类图

1.Executor类图

顶层节点Executor,BaseExecutor中使用了一级缓存PerpetualCache,一共有四个子类,类图中还没标全,分别是SimpleExecutor简单执行器、ReuseExecutor可重用执行器(重用同一个Statement对象)、BatchExecutor批处理执行器、ClosedExecutor关闭的执行器(ResultLoaderMap的内部类,用于标记序列化反序列化后执行器处于关闭状态)。CachingExecutor为二级缓存执行器,包含了二级缓存Cache

 2.Executor的访问流程

创建SqlSession调用query方法或者update方法访问二级缓存,再访问BaseExecutor,通过配置文件中指定的执行器类别,或者构造的执行器来执行具体的doQuery方法或者doUpdate方法。Executor通过装饰者模式使用delegate对象向下传递。

二:BaseExecutor和一级缓存

1.BaseExecutor

基本功能:改查、通过提供接口做规范query、update,子类通过doQuery、doUpdate来实现具体功能。

特点:生命周期较短,存储在内存中使用(hashmap),不需要考虑存储大小。

四个子类:

1.简单执行器SimpleExecutor:每次都要构建一个新的prepareStatement对象,对sql语句进行编译

        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xx", "root", "123456");
    	tran = new JdbcTransaction(conn);
    	SimpleExecutor executor = new SimpleExecutor(configuration, tran);
    	MappedStatement ms = configuration.getMappedStatement("com.crw.mybatis.mapper.StudentMapper.getById");
    	List<Object> list = executor.doQuery(ms, 1, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));

 2.重用执行器ReuseExecutor:利用原有的prepareStatement,只执行一次编译,设置了两次参数

3.批处理执行器BatchExecutor:只针对增删改操作的批操作,最后需调用flush提交到数据库

4.已关闭的执行器ClosedExecutor:在序列化反序列化后生成,代表执行器已关闭

2.一级缓存PerpetualCache

命中场景:

1.命名空间、方法、参数 都要一样

2.sqlsession也要一样(一级缓存是会话级缓存)

3.rowBounds分页必须相同

失效场景:

1.sqlSession.clearCache();或者mapper的方法或xml配置了flushCache="true"

2.执行了插入更新操作,会清空sqlSession下所有的缓存

3.缓存作用域改成Statement,执行完statement关闭时就清空缓存

3.源码分析

BaseExecutor的Query源码分析:

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取动态Sql
BoundSql boundSql = ms.getBoundSql(parameter);
//生成一级缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
 @SuppressWarnings("unchecked")

 @Override

 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.");

  }

//清空缓存标签要跟父查询一起才能生效,不然嵌套查询就会出现循环依赖的问题
//queryStack用于标记这是嵌套查询的第几层
  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--;

  }

//嵌套查询中的栈,第一层是0,后面1-2-3递推

//填充缓存或者获取到了真正的值后,且会到了父查询栈时,才执行延迟加载操作

  if (queryStack == 0) {

   for (DeferredLoad deferredLoad : deferredLoads) {
//延迟队列中的元素,调用load方法
    deferredLoad.load();

   }

   // issue #601

   deferredLoads.clear();

   if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {

    // issue #482

    clearLocalCache();

   }

  }

  return list;

 }

缓存key的格式

-325093600:3010135970:com.crw.mybatis.mapper.StudentMapper.getById:0:2147483647:select id,age,name from student where id = ?:1:development

查不到缓存则执行queryFromDatabase方法

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

  List<E> list;

//先往缓存中填充占位符,EXECUTION_PLACEHOLDER,与嵌套查询相关

  localCache.putObject(key, EXECUTION_PLACEHOLDER);

  try {
//执行具体的执行器如SimpleExecutor.doQuery方法真实查询
   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;

 }

触发清空一级缓存的四个操作:

1.update

public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
   throw new ExecutorException("Executor was closed.")
  }
//清空缓存
  clearLocalCache();
  return doUpdate(ms, parameter);
 }

2.query中配置flushcache true且queryStack为0,也就是在执行子查询之前(子查询中不清空)

if (queryStack == 0 && ms.isFlushCacheRequired()) {
//清空缓存
   clearLocalCache();
  }

3.session作用域statement

if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    clearLocalCache();
   }

4.手动提交或回滚

@Override
 public void commit(boolean required) throws SQLException {
  if (closed) {
   throw new ExecutorException("Cannot commit, transaction is already closed");
  }
  clearLocalCache();
  flushStatements();
  if (required) {
   transaction.commit();
  }
 }

 @Override
 public void rollback(boolean required) throws SQLException {
  if (!closed) {
   try {
    clearLocalCache();
    flushStatements(true);
   } finally {
    if (required) {
     transaction.rollback();
    }
   }
  }
 }

三:CachingExecutor和二级缓存

1.二级缓存简介

特点:

1.应用级缓存:生命周期贯穿整个应用,跨线程使用(会话不是线程安全的,所以一级缓存是每个会话生命周期内完成,就没有线程安全问题),场景要求命中率更高,改动更小

2.存储:需要考虑存储方式(内存、硬盘)、淘汰策略

3.可以与第三方缓存集成:目的做分布式缓存

实现方式:

Cache责任链模式、装饰者模式

每个会话都会自己的事务缓存管理器,每访问一个缓存空间,就会生成一个暂存区,当执行提交操作后,将暂存区的数据同步到真实二级缓存之中

对象结构如下:

缓存命中情况:

1.会话提交后(flushCache等操作后,都要提交才可以生效)

2.sql语句、参数相同

3.相同的statementId:方法名

4.RowBounds相同

命中率统计:

2.二级缓存源码分析

query方法:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
   throws SQLException {
  Cache cache = ms.getCache();//1.缓存打开。2.声明缓存空间 不然就为null
  if (cache != null) {
//是否要清空缓存
   flushCacheIfRequired(ms);
   if (ms.isUseCache() && resultHandler == null) {
    ensureNoOutParams(ms, boundSql);
    @SuppressWarnings("unchecked")
//事务缓存管理器中获取对象
//Map<Cache, TransactionalCache> transactionalCaches.getObject(key) 通过cache获取到暂存区缓存TransactionalCache,再从中通过key获取到对应的值
    List<E> list = (List<E>) tcm.getObject(cache, key);
    if (list == null) {
//交给下级查询 下级可能是SimpleExecutor
     list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//当查询到数据后,数据先放入暂存区中,commit再放入二级缓存
     tcm.putObject(cache, key, list); // issue #578 and #116
    }
    return list;
   }
  }
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
 }

 transactionalCaches.getObject方法:

public Object getObject(Object key) {
    // issue #116
//一旦查到存在暂存区,那么直接调用下一个链条查找缓存,查到的是真实二级缓存中的数据,因为暂存区的数据都是变更后的数据,直接查出意味着脏读
    Object object = delegate.getObject(key);
    if (object == null) {
//如果查到二级缓存没有数据,则在列表中加入key,来防止缓存击穿
      entriesMissedInCache.add(key);
    }
    // issue #146
//就算二级缓存有数据 clearOnCommit参数为true也返回null
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

commit操作:

//遍历暂存区数据刷入缓存区中
private void flushPendingEntries() {
  for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
   delegate.putObject(entry.getKey(), entry.getValue());
  }
  for (Object entry : entriesMissedInCache) {
   if (!entriesToAddOnCommit.containsKey(entry)) {
    delegate.putObject(entry, null);
   }
  }
 }

缓存责任链:

1.线程同步、安全Synchronizedcache

//方法加上了同步标签

public synchronized Object getObject(Object key) {
  return delegate.getObject(key);
 }

2.记录命中率loggingCache

加上了命中log日志
public Object getObject(Object key) {
  requests++;
  final Object value = delegate.getObject(key);
  if (value != null) {
   hits++;
  }
  if (log.isDebugEnabled()) {
   log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
  }
  return value;
 }

3.淘汰LRUCache:默认溢出淘汰策略

 public void setSize(final int size) {
//默认淘汰最老的节点
  keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
   private static final long serialVersionUID = 4267176411845948333L;
   @Override
   protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
    boolean tooBig = size() > size;
    if (tooBig) {
     eldestKey = eldest.getKey();
    }
    return tooBig;
   }
  };
 }

4.过期清理ScheduledCache

private boolean clearWhenStale() {
//判断超时
  if (System.currentTimeMillis() - lastClear > clearInterval) {
   clear();
   return true;
  }
  return false;
 }

5.防穿透BlockingCache(阻塞缓存)

private void acquireLock(Object key) {
  CountDownLatch newLatch = new CountDownLatch(1);
  while (true) {
//判断当前资源是否有其他线程访问,如果没有就是null
//ReentrantLock对资源上锁
   CountDownLatch latch = locks.putIfAbsent(key, newLatch);
   if (latch == null) {
    break;
   }
   try {
    if (timeout > 0) {
     boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS);
     if (!acquired) {
//指定时间内不能获取到锁则抛出异常
      throw new CacheException(
        "Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
     }
    } else {
     latch.await();
    }
   } catch (InterruptedException e) {
    throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
   }
  }
 }

6.最终存在内存中的PerpetualCache

private final Map<Object, Object> cache = new HashMap<>();

使用map存储

结尾感谢鲁班大叔,从他那里学到了很多Mybatis相关知识。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值