Mybatis源码阅读学习笔记(一)之Executor子类BaseExecutor源码超详细解析

  • Executor是一个大管家,核心功能包括:缓存维护、获取动态SQL、获取连接、以及最终的JDBC调用等。在这里插入图片描述
    Executor接口及其层级如下:
    在这里插入图片描述BaseExecutor基础执行器,包括一级缓存逻辑也在此实现,BaseExecutor是Excutor接口的抽象实现类,主要提供了缓存管理事务管理的基本功能,其实现了接口Executor的部分方法,SimpleExecutor继承了BaseExecutor抽象类,是Mybatis提供的最简单的Executor接口实现;ReuseExecutor也继承了BaseExecutor抽象类,提供了对Statement重用的功能BatchExecutor同样继承了BaseExecutor抽象类,实现了批处理多条 SQL 语句的功能ClosedExecutor也继承了BaseExecutor抽象类,且是ResultLoaderMap类中的一个内部类,用于实现懒加载相关逻辑CachingExecutor类直接实现了Excutor接口,是装饰器类,主要增强缓存相关功能

我们今天先阅读BaseExecutor的源码

BaseExecutor抽象类的源码及详解注释如下,源码较多,但注释也多,有需要理解的请耐心看或跳着看所需要的那一部分,此源码注释只为了记录个人学习笔记,方便自查

/**

 * @author Clinton Begin
 */
public abstract class BaseExecutor implements Executor {

  //日志
  private static final Log log = LogFactory.getLog(BaseExecutor.class);
  //Transaction对象,实现事务的提交、回滚和关闭操作
  //protected对于类中来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private
  protected Transaction transaction;
  //封装了真正的 Executor对象
  protected Executor wrapper;
  //定义线程安全队列,此类继承和实现如下
  /*public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
        implements Queue<E>, java.io.Serializable {
  延迟加载队列。一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)原则对元素进行排序。
  当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许使用 null元素。该变量主要是存储一些可以延时加载的变量对象,且可供多线程使用*/
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  //一级缓存,用于缓存该 Executor对象查询结果集映射得到的结果对象,此类中成员如下
  /*public class PerpetualCache implements Cache {
  private final String id;
  private Map<Object, Object> cache = new HashMap<>();
  */
  protected PerpetualCache localCache;
  //一级缓存,用于缓存输出类型的参数
  protected PerpetualCache localOutputParameterCache;
  //mybatis的配置信息,全局唯一配置对象
  protected Configuration configuration;
  //查询的深度,用来记录嵌套查询的层数,分析 DefaultResultSetHandler时介绍过的嵌套查询
  protected int queryStack = 0;
  //标识该执行器是否已经关闭
  private boolean closed;
  
  //BaseExecutor类构造函数,初始化变量,主要实现了对上述的属性字段进行初始化工作。其中,
  //locaCache、localOutputParameterCache都使用了PerpetualCache类型的缓存。
  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    //事务对象
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    //表明exector的状态
    this.closed = false;
    //主文件属性,主要获取MappedStatement对象
    this.configuration = configuration;
    this.wrapper = this;
  }
  //获取执行器中的事务对象
  @Override
  public Transaction getTransaction() {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    return transaction;
  }
   /**
   * 关闭当前执行器,如果需要回滚,就执行回滚操作
   * 需要把相应的资源(比如事务transaction等),同步关闭
   * 把其他变量设置成NULL
   */
  @Override
  public void close(boolean forceRollback) {
    try {
      try {
        rollback(forceRollback);
      } finally {
        if (transaction != null) {
          transaction.close();
        }
      }
    } catch (SQLException e) {
      // Ignore.  There's nothing that can be done at this point.
      log.warn("Unexpected exception on closing transaction.  Cause: " + e);
    } finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;
      localOutputParameterCache = null;
      closed = true;
    }
  }
  //获取当前执行器是否关闭的标识	
  @Override
  public boolean isClosed() {
    return closed;
  }
    /*update/insert/delete请求到达SqlSession都会调用此方法
    从语义的角度,insert、update、delete都是属于对数据库的行进行更新操作
    从实现的角度,我们熟悉的PreparedStatement里面提供了两种execute方法,一种是executeUpdate(),
    一种是executeQuery(),前者对应的是insert、update与delete,后者对应的是select,因此对于MyBatis来说只有update与select
    该方法处理了通用的情况,真正实现由子类通过实现doUpdate方法完成。该方法实现了:
    判断该执行器是否已经关闭,如果关闭,就直接抛出异常
    因为该操作是更新操作,所以该操作会让其中的缓存就全部失效,即清理缓存
    调用抽象方法doUpdate实现数据更新,由子类完成。
    */
  @Override
  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.");
    }
    //由于默认情况下mybatis开启一级缓存,所以如果你需要每次查询都从数据库查询
   //清空一级缓存,基于SqlSession范围作用,再更新,如何更新由子类实现,这里利用模板方法模式
    clearLocalCache();
     //调用抽象方法doUpdate实现数据更新,由子类完成
    return doUpdate(ms, parameter);
  }
	//刷新Statement。真正的实现由具体的子类完成,且在不同的实现类中的逻辑不一样
  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return flushStatements(false);
  }
  //调用批处理执行器中方法,刷新Statement,记录执行次数
  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //调用抽象方法doFlushStatements,真正实现由子类完成
    return doFlushStatements(isRollBack);
  }
  /*SqlSession.selectList会调用此方法
  在该方法中,首先判断当前执行器是否已经关闭;然后再根据queryStack查询层数和flushCache属性判断,
  是否需要清除一级缓存;然后再判断结果处理器resultHandler是否为空,为空的话,尝试从缓存中查询数据,
  否则直接为空,后续从数据库查询数据;然后再根据缓存中是否有数据,如果存在数据,
  且是存储过程或函数类型则执行handleLocallyCachedOutputParameters()方法,如果存在数据,
  不是存储过程或函数类型就直接返回,如果缓存中没有数据就直接通过queryFromDatabase()方法从数据库查询数据,
  其中又通过doQuery()方法实现查询逻辑;后续在通过DeferredLoad类实现嵌套查询的延时加载功能*/
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	//得到绑定sql,并将参数对象与sql语句的#{}一一对应
    BoundSql boundSql = ms.getBoundSql(parameter);
    //创建获取cacheKey供缓存,包含完整的语句、参数等,确保CacheKey的唯一性
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    //比原先多传入CacheKey和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.");
    }
     //判断是否清除本地缓存,但仅仅查询堆栈为0才清,为了处理递归调用
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      //本地缓存记录自增,这样递归调用到上面的时候就不会再清局部缓存了
      queryStack++;
      //如果查询的语句已存在本地缓存中,则直接从本地获取,反之从数据库中读取内容
      list = resultHandler == null ? (List) localCache.getObject(key) : null;
      if (list != null) {
		/*针对存储过程调用的处理 其功能是 在一级缓存命中时,获取缓存中保存的输出类型参数,
    	并设到用户传入的实参( parameter )对象中。
        */
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
	    //从数据库中获取并进行缓存处理,其也会调用子类需复写的doQuery()方法
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
	  //清空堆栈操作
      queryStack--;
    }
    if (queryStack == 0) {
	  //延迟加载队列中所有元素
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      //清空延迟加载队列
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        //如果是statement,清本地缓存
        clearLocalCache();
      }
    }
    return list;
  }
  //查询,返回Cursor类型。该方法主要完成了根据参数生成BoundSql实例的工作,真正的查询工作还是由子类实现doQueryCursor方法来完成。
  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {

    BoundSql boundSql = ms.getBoundSql(parameter);
    return doQueryCursor(ms, parameter, rowBounds, boundSql);
  }
	//延迟加载一级缓存中的数据
  @Override
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //创建DeferredLoad对象
    DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
    	//如果能加载则立即加载,否则加入到延迟加载队列中
    if (deferredLoad.canLoad()) {
      deferredLoad.load();
    } else {
      deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
    }
  }
   /*
   createCacheKey()方法
   创建缓存中使用的key,即CacheKey实例对象。创建的CacheKey实例对象,由下列参数决定其唯一性,
   即相同的查询,相关的参数,生成的CacheKey实例唯一且不变。依据的参数如下:
   1、MappedStatement实例的ID
   2、RowBounds实例的offset和limit属性
   3、BoundSql实例的sql属性
   4、查询条件的参数值
   5、连接数据库的环境ID
   MappedStatement(一般为xml中定义)、parameterObject(传递给xml的参数)、
   rowBounds(分页参数)、boundSql(最终sql,由MappedStatement和parameterObject决定)
	*/
	//还有如果一个查询的 id、分页组件中的 offset 和 limit、sql 语句、参数 都保持不变,
	//那么这个查询产生的 CacheKey一定是不变的。在一个 SqlSession 的生命周期内,二次同样的查询 CacheKey 是一样的
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) throw new ExecutorException("Executor was closed.");
    CacheKey cacheKey = new CacheKey();
	/*MyBatis对于其Key的生成采取规则为:
	//[mappedStementId + offset + limit + SQL + queryParams + environment]生成一个哈希码
	createCacheKey方法中调用CacheKey的update方法时传入的元素,就是Mybatis用来确定缓存唯一性的元素
	*/
    //方法名
    cacheKey.update(ms.getId());
    //逻辑分页偏移量
    cacheKey.update(rowBounds.getOffset());
    //逻辑分页起始值
    cacheKey.update(rowBounds.getLimit());
    //BoundSql实例的sql属性
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {

      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }
  //判断该查询结果是否被缓存。
  @Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    return localCache.getObject(key) != null;
  }
  /**
   * 事务提交,真正提交操作是通过事务实例transaction来完成
   * 该方法实现了:
   * 1、判断执行器是否被关闭,如果关闭就直接抛出异常
   * 2、清理缓存和执行预操作
   * 3、根据参数判断是否执行提交操作
   */
  @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();
        }
      }
    }
  }
	//清空本地缓存,一个map结构
  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
	//调用抽象方法doFlushStatements,真正实现由子类完成
  protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
      throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;
//根据参数生成BoundSql实例的工作,真正的查询工作还是由子类实现doQueryCursor方法来完成
  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;
      
	//关闭java的Statement实例
  protected void closeStatement(Statement statement) {
    if (statement != null) {
      try {
        statement.close();
      } catch (SQLException e) {
        // ignore
      }
    }
  }
   * Apply a transaction timeout.
   * @param statement a current statement
   * @throws SQLException if a database access error occurs, this method is called on a closed <code>Statement</code>
   * @since 3.4.0
   * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
   */
  protected void applyTransactionTimeout(Statement statement) throws SQLException {
  //第一优先级是事务中设置的超时时间
    StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
  }
//针对存储过程调用的处理 其功能是 在一级缓存命中时,获取缓存中保存的输出类型参数,并设豆到用户传入的实参( parameter )对象中。
   * @param ms
  private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
	CALLABLE类型,即存储过程、函数类型,否则不做任何处理
    if (ms.getStatementType() == StatementType.CALLABLE) {
	/*从localOutputParameterCache取出缓存对象,localOutputParameterCache就是一个PerpetualCache
	// PerpetualCache维护了个map,就是session的缓存本质了,类成员如下:
	public class PerpetualCache implements Cache {
    private final String id;
    private Map<Object, Object> cache = new HashMap<>();
	*/
      final Object cachedParameter = localOutputParameterCache.getObject(key);
      if (cachedParameter != null && parameter != null) {
		//元数据对象(MetaObject)实际上就是提供 类|集合|Map 的一种自动识别的访问形式
        final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
        final MetaObject metaParameter = configuration.newMetaObject(parameter);
        //通过boundSql去获取parameterMappings这个集合,循环使用parameterMapping 接收
        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
			/*
			当OUT, INOUT 才继续处理
			public enum ParameterMode {
 			 IN, OUT, INOUT
			}
			*/
          if (parameterMapping.getMode() != ParameterMode.IN) {
			//获取参数名称,通过参数名称获取参数值,再存入
            final String parameterName = parameterMapping.getProperty();
            final Object cachedValue = metaCachedParameter.getValue(parameterName);
            metaParameter.setValue(parameterName, cachedValue);
          }
        }
      }
    }
  }
    //从数据库中查出数据
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    	//向缓存中放入占位符
    	/*
    	public enum ExecutionPlaceholder {
 		EXECUTION_PLACEHOLDER
		}
    	*/
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
		//通过调用需要子类实现的抽象方法doQuery()实现真正的查询操作逻辑
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
		  //执行完查询后清除占位符
      localCache.removeObject(key);
    }
    	//把查询数据加入缓存
    localCache.putObject(key, list);
    	//如果是存储过程,OUT参数也加入缓存
    	/*public enum StatementType {
 		  STATEMENT, PREPARED, CALLABLE
 		  声明       准备好的    可调用的 
		  }
    	*/
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
  //mybatis在获取连接的时候,会根据日志的打印级别来判断是否会创建一个代理类。到这里就基本可以猜到,
  //在代理类中,mybatis会去打印这个sql的语句
  protected Connection getConnection(Log statementLog) throws SQLException {
	//获取连接
    Connection connection = transaction.getConnection();
    //如果是Debug级别,创建一个代理类
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

  @Override
  public void setExecutorWrapper(Executor wrapper) {
    this.wrapper = wrapper;
  }
	/*内部类DeferredLoad
	负责从一级缓存中延迟加载结果对象,并赋给外层对象
	*/
  private static class DeferredLoad {
    //结果对象(外层对象)对应的MetaObject对象
    private final MetaObject resultObject;
    //延迟加载的属性名称
    private final String property;
    //延迟加载的属性类型
    private final Class<?> targetType;
    //结果对象在缓存中的key
    private final CacheKey key;
    //一级缓存
    private final PerpetualCache localCache;
    //默认对象工厂
    private final ObjectFactory objectFactory;
    //将结果从集合类型转为对象类型的辅助类
    private final ResultExtractor resultExtractor;

    // issue #781  构造函数
    public DeferredLoad(MetaObject resultObject,
                        String property,
                        CacheKey key,
                        PerpetualCache localCache,
                        Configuration configuration,
                        Class<?> targetType) {

      this.resultObject = resultObject;
      this.property = property;
      this.key = key;
      this.localCache = localCache;
      this.objectFactory = configuration.getObjectFactory();
      this.resultExtractor = new ResultExtractor(configuration, objectFactory);
      this.targetType = targetType;
    }
	//用于检测缓存对象是否已经加载到缓存中,在缓存中找到,不空且不为占位符,代表可以加载
    public boolean canLoad() {
      return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
    }
	//负责从缓存中获取对象,并将其设置到外层对象中
    public void load() {
      @SuppressWarnings("unchecked")
      // we suppose we get back a List
      List<Object> list = (List<Object>) localCache.getObject(key);
      Object value = resultExtractor.extractObjectFromList(list, targetType);
      resultObject.setValue(property, value);
    }

  }

}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值