Mybatis源码分析(4) -- Executor分析

Mybatis源码分析(4) -- Executor分析

这一篇我们要分析一下Executor的功能,先看看Executor接口提供了那些功能方法:

public interface Executor {

	ResultHandler NO_RESULT_HANDLER = null;
	
	//执行修改操作
	int update(MappedStatement ms, Object parameter) throws SQLException;
	
	//执行查询操作(带缓存key)
	<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
	
	//执行查询操作
	<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
	
	//刷新批量执行的语句,并返回批量执行的结果集合
	List<BatchResult> flushStatements() throws SQLException;
	
	//提交事务
	void commit(boolean required) throws SQLException;
	
	//回滚事务
	void rollback(boolean required) throws SQLException;
	
	//创建CacheKey
	CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
	
	//是否被缓存
	boolean isCached(MappedStatement ms, CacheKey key);
	
	//清除缓存
	void clearLocalCache();
	
	//延迟加载
	void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
	
	//获取事务
	Transaction getTransaction();
	
	//关闭事务
	void close(boolean forceRollback);
	
	//事务是否已经被关闭
	boolean isClosed();

}

从Executor接口功能来分析,Executor主要提供了以下几个功能:

  1. 查询、修改数据库操作
  2. 提供了缓存功能
  3. 获取事务以及对事务操作

Executor接口的实现图是这样的:

Executor
   |-------BaseExecutor(基础抽象执行器)
			   |-----------SimpleExecutor(简单执行器)
			   |-----------ReuseExecutor(重用执行器)
			   |-----------BatchExecutor(批量执行器)
   |-------CachingExecutor(缓存执行器)

下面我们逐一分析一下各个实现类,看看各个子类的功能是如何实现的。

BaseExecutor 基础抽象执行器

BaseExecutor是对Executor最基础的实现,这个类比较简单,下面以query操作简单的分析一下。

先看看最常用的query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)方法。这个方法负责查询操作。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	//根据参数对象获取到BoundSql,BoundSql包装了执行的sql和参数信息
	BoundSql boundSql = ms.getBoundSql(parameter);
	//创建CacheKey用于获取缓存结果(当作hashcode的计算就可以了)
	CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
	//查询数据
	return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

@SuppressWarnings("unchecked")
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();
	  }
	  deferredLoads.clear(); // issue #601
	  if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
		clearLocalCache(); // issue #482
	  }
	}
	return list;
}

下面是从数据库中获取数据。

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

接下来看看子类中的doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)是怎样实现的。

SimpleExecutor 简单执行器

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
	Statement stmt = null;
	try {
	  //获取Configuration
	  Configuration configuration = ms.getConfiguration();
	  ////创建StatementHanlder,这里使用了拦截器代理
	  StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
	  //生成Statement对象
	  stmt = prepareStatement(handler, ms.getStatementLog());
	  //真正的查询操作是由StatementHandler来完成的
	  return handler.update(stmt);
	} finally {
	  closeStatement(stmt);
	}
}

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
	Statement stmt = null;
	try {
	  //获取Configuration
	  Configuration configuration = ms.getConfiguration();
	  //创建StatementHanlder,这里使用了拦截器代理
	  StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
	  //生成Statement对象
	  stmt = prepareStatement(handler, ms.getStatementLog());
	  //真正的查询操作是由StatementHandler来完成的
	  return handler.<E>query(stmt, resultHandler);
	} finally {
	  closeStatement(stmt);
	}
}

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

ReuseExecutor 重用执行器

首先我们要明白,这里的重用指的是什么?这里的重用指的是Statement对象的重用。也就是说,对于相同的sql,如果Statement对象还存在的话,则使用之前存在的Statement对象。

public class ReuseExecutor extends BaseExecutor {

	//缓存Statement
	private final Map<String, Statement> statementMap = new HashMap<String, Statement>();
	
	public ReuseExecutor(Configuration configuration, Transaction transaction) {
		super(configuration, transaction);
	}
	
	public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
		Configuration configuration = ms.getConfiguration();
		StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
		//获取Statement对象(可以重用之前存在的Statement)
		Statement stmt = prepareStatement(handler, ms.getStatementLog());
		return handler.update(stmt);
	}
	
	public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
		Configuration configuration = ms.getConfiguration();
		StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
		Statement stmt = prepareStatement(handler, ms.getStatementLog());
		return handler.<E>query(stmt, resultHandler);
	}
	
	/**
	 * 提交事务的时候会调用这个方法,关闭Statement,清除缓存
	 */
	public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
		for (Statement stmt : statementMap.values()) {
			closeStatement(stmt);
		}
		statementMap.clear();
		return Collections.emptyList();
	}
	
	private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
		Statement stmt;
		BoundSql boundSql = handler.getBoundSql();
		String sql = boundSql.getSql();

		//如果Statement没有被关闭,则从缓存中获取,重新使用Statement
		if (hasStatementFor(sql)) {
			stmt = getStatement(sql);
		} else {
			//创建新的Statement,并放入到缓存中去
			Connection connection = getConnection(statementLog);
			stmt = handler.prepare(connection);
			putStatement(sql, stmt);
		}

		//设置预处理参数
		handler.parameterize(stmt);
		return stmt;
	}
	
	private boolean hasStatementFor(String sql) {
		try {
			//判断sql是否被缓存,并且该sql缓存的Statement没有被关闭
			return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
		} catch (SQLException e) {
			return false;
		}
	}
	
	private Statement getStatement(String s) {
		return statementMap.get(s);
	}
	
	private void putStatement(String sql, Statement stmt) {
		statementMap.put(sql, stmt);
	}

}

BatchExecutor 批量执行器

在讲解BatchExecutor先熟悉一下JDBC中是怎么进行批量处理的,另外,批量操作只对插入、修改、删除有效。下面看看Statment和PreparedStatement的批量操作流程:

Statement

  1. addBatch(String sql)方法会在批处理缓存中加入一条sql语句。<br>
  2. executeBatch()执行批处理缓存中的所有sql语句。<br>
//创建Statment对象
Statment statment = con.createStatement();
//添加第一个语句
statment.addBatch("insert into student values(23,'tangbao','高数',100)");
//添加第二个语句
statment.addBatch("insert into student values(24,'王定','c#',98)");
//批量执行
statment.executeBatch();

PreparedStatement

  1. 设置参数
  2. addBatch()将一组参数添加到PreparedStatement对象内部。
  3. executeBatch()将一批参数提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。
//创建PreparedStatement对象
PreparedStatement preparedStatment = con.prepareStatement("insert into student values(?,?,?,?)");   
//设置第一个参数,提交批量
preparedStatment.setInt(1, 33); 
preparedStatment.setString(2,"wangqin");   
preparedStatment.setString(3, "c++");   
preparedStatment.setDouble(4, 78.5);   
pstm.addBatch();   
//设置第二个参数,提交批量
preparedStatment.setInt(1, 34);   
preparedStatment.setString(2,"wuytun");   
preparedStatment.setString(3, "c");   
preparedStatment.setDouble(4, 77);   
pstm.addBatch();
//批量执行   
pstm.executeBatch();   

关于JDBC批量操作的流程大致就是上面这样的了,接下来我们看看BatchExecutor是如何实现的。

public class BatchExecutor extends BaseExecutor {

	//批量修改时的返回值(感觉这个没有什么用)
	public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
	
	//保存执行Statment集合
	private final List<Statement> statementList = new ArrayList<Statement>();
	//保存批量操作后的结果集合
	private final List<BatchResult> batchResultList = new ArrayList<BatchResult>();
	//最近一次执行的语句
	private String currentSql;
	//最近一次执行语句的MappedStatment对象
	private MappedStatement currentStatement;
	
	public BatchExecutor(Configuration configuration, Transaction transaction) {
		super(configuration, transaction);
	}
	
	public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
		final Configuration configuration = ms.getConfiguration();
		final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
		final BoundSql boundSql = handler.getBoundSql();
		final String sql = boundSql.getSql();
		final Statement stmt;
		//如果执行语句和最近一次相同,则获取到上次的Statment和BatchResult,并把参数对象设置到BatchResult中
		if (sql.equals(currentSql) && ms.equals(currentStatement)) {
			int last = statementList.size() - 1;
			stmt = statementList.get(last);
			BatchResult batchResult = batchResultList.get(last);
			batchResult.addParameterObject(parameterObject);
		} else {
			//创建新的Statment和对应的BatchResult
			Connection connection = getConnection(ms.getStatementLog());
			stmt = handler.prepare(connection);
			currentSql = sql;
			currentStatement = ms;
			statementList.add(stmt);
			batchResultList.add(new BatchResult(ms, sql, parameterObject));
		}

		//设置参数
		handler.parameterize(stmt);

		//调用批量,这里就是调用了Statment#addBatch(sql)或PreparedStatement#addBatch()方法
		handler.batch(stmt);
		return BATCH_UPDATE_RETURN_VALUE;
	}
	
	public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
	throws SQLException {
		Statement stmt = null;
		try {

			//刷新Statment,也就是调用下面的doFlushStatements(false),先批量执行掉之前的缓存语句操作
			flushStatements();

			//查询操作不涉及批量操作,和SimpleExecutor中的处理一样
			Configuration configuration = ms.getConfiguration();
			StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, rowBounds, resultHandler, boundSql);
			Connection connection = getConnection(ms.getStatementLog());
			stmt = handler.prepare(connection);
			handler.parameterize(stmt);
			return handler.<E>query(stmt, resultHandler);
		} finally {
			closeStatement(stmt);
		}
	}
	
	public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
		try {
			List<BatchResult> results = new ArrayList<BatchResult>();
			if (isRollback) {
				return Collections.emptyList();
			} else {

				//遍历Stament
				for (int i = 0, n = statementList.size(); i < n; i++) {
					//获取到存储的Statment和对应的BatchResult
					Statement stmt = statementList.get(i);
					BatchResult batchResult = batchResultList.get(i);
					try {
						//调用Statment#executeBatch()或PreparedStatement#executeBatch()方法批量执行,并将执行结果设置到BatchResult中去。
						batchResult.setUpdateCounts(stmt.executeBatch());
						MappedStatement ms = batchResult.getMappedStatement();
						List<Object> parameterObjects = batchResult.getParameterObjects();
						KeyGenerator keyGenerator = ms.getKeyGenerator();
						if (keyGenerator instanceof Jdbc3KeyGenerator) {
							Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
							jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
						} else {
							for (Object parameter : parameterObjects) {
								keyGenerator.processAfter(this, ms, stmt, parameter);
							}
						}
					} catch (BatchUpdateException e) {
						StringBuffer message = new StringBuffer();
						message.append(batchResult.getMappedStatement().getId())
							.append(" (batch index #")
							.append(i + 1)
							.append(")")
							.append(" failed.");
						if (i > 0) {
							message.append(" ")
							.append(i)
							.append(" prior sub executor(s) completed successfully, but will be rolled back.");
						}
						throw new BatchExecutorException(message.toString(), e, results, batchResult);
					}
					//添加批量结果
					results.add(batchResult);
				}

				//返回批量结果集合
				return results;
			}
		} finally {

			//关闭掉Stament对象,并清空存储的Statment和BatchResult集合
			for (Statement stmt : statementList) {
				closeStatement(stmt);
			}
			currentSql = null;
			statementList.clear();
			batchResultList.clear();
		}
	}

}

CachingExecutor

首先,我们要清楚CachingExecutor是直接实现Executor接口的,并没有继承BaseExecutor。CacheingExecutor只是委派了一个Executor来执行,也就是委派给BaseExecutor的具体实现类来处理。很显然这里使用了设计模式中的包装模式,将BaseExecutor进行包装处理,添加了缓存功能。

public class CachingExecutor implements Executor {

	//被包装的BaseExecutor
	private Executor delegate;

	//是否自动提交
	private boolean autoCommit;

	//事务缓存管理器,管理事务的提交回滚
	private TransactionalCacheManager tcm = new TransactionalCacheManager();
	
	//缓存功能开关标志位
	private boolean dirty;
	
	public CachingExecutor(Executor delegate) {
		this(delegate, false);
	}
	
	public CachingExecutor(Executor delegate, boolean autoCommit) {
		this.delegate = delegate;
		this.autoCommit = autoCommit;
	}
	
	public Transaction getTransaction() {
		return delegate.getTransaction();
	}
	
	public void close(boolean forceRollback) {
		try {
		//如果缓存关闭并且不需要自动提交,则进行回滚
		if (dirty && !autoCommit) { 
			tcm.rollback();
		} else {
			//自动提交
			tcm.commit();
		}
		} finally {
			//关闭事务
			delegate.close(forceRollback);
		}
	}
	
	public boolean isClosed() {
		return delegate.isClosed();
	}
	
	public int update(MappedStatement ms, Object parameterObject) throws SQLException {
		//刷新缓存
		flushCacheIfRequired(ms);
		return delegate.update(ms, parameterObject);
	}
	
	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);
	}
	
	public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

		//Cache对象是在什么时候设置的呢?
		Cache cache = ms.getCache();
		if (cache != null) {
			//刷新缓存
			flushCacheIfRequired(ms);
			if (ms.isUseCache() && resultHandler == null) { 
				ensureNoOutParams(ms, parameterObject, boundSql);
				if (!dirty) {
					cache.getReadWriteLock().readLock().lock();
					try {
						//从缓存中获取数据,如果存在则返回
						List<E> cachedList = (List<E>) cache.getObject(key);
						if (cachedList != null) return cachedList;
					} finally {
						cache.getReadWriteLock().readLock().unlock();
					}
				}
				//如果缓存开关关闭(dirty=true),或者缓存中没有获取到数据,则从数据据查询数据
				List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
				//将查询的结果放入到缓存中去
				tcm.putObject(cache, key, list);
				//返回查询数据
				return list;
			}
		}

		//不使用缓存功能,直接从数据库查询数据
		return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
	}
	
	public List<BatchResult> flushStatements() throws SQLException {
		return delegate.flushStatements();
	}
	
	public void commit(boolean required) throws SQLException {
		delegate.commit(required);
		tcm.commit();
		dirty = false;
	}
	
	public void rollback(boolean required) throws SQLException {
		try {
			delegate.rollback(required);
			dirty = false;
		} finally {
			if (required) {
				tcm.rollback();
			}
		}
	}
	
	//针对存储过程调用时的检测
	private void ensureNoOutParams(MappedStatement ms, Object parameter, BoundSql boundSql) {
		if (ms.getStatementType() == StatementType.CALLABLE) {
			for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
				if (parameterMapping.getMode() != ParameterMode.IN) {
					throw new ExecutorException("Caching stored procedures with OUT params is not supported.  Please configure useCache=false in " + ms.getId() + " statement.");
				}
			}
		}
	}
	
	public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
		return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
	}
	
	public boolean isCached(MappedStatement ms, CacheKey key) {
		throw new UnsupportedOperationException("The CachingExecutor should not be used by result loaders and thus isCached() should never be called.");
	}
	
	public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
		throw new UnsupportedOperationException("The CachingExecutor should not be used by result loaders and thus deferLoad() should never be called.");
	}
	
	public void clearLocalCache() {
		delegate.clearLocalCache();
	}
	
	private void flushCacheIfRequired(MappedStatement ms) {
		Cache cache = ms.getCache();
		//如果既有缓存对象,又需要刷新缓存,则标志缓存功能关闭
		if (cache != null && ms.isFlushCacheRequired()) {
			dirty = true;
			tcm.clear(cache);
		}
	}

}

Executor的源码我们都已经分析的差不多了,Executor接口有四中具体的子类,那么,Mybatis应该使用哪一种呢?或者说怎样才能指定使用哪一种Executor呢?关于Executor的创建过程可以查看 Mybatis源码分析(1) -- SqlSession的创建

另外,在上面分析doQuery方法时发现Cache cache = ms.getCache(),只有cache不为null是才能进行缓存查询。那么,cache是什么时候设置的呢? 这里的ms就是MappedStatement对象啦,所以我们会到之前分析MappedStatement创建的地方。MappedStatement是由MapperBuilderAssistant#addMappedStatement()创建的,MappedStatement的创建在前面已经分析过了,就不赘述了。可以参考Mybatis源码分析(3) -- Mapper文件解析。在创建MappedStatment时调用了MapperBuilderAssistant#setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder)用来创建Cache对象。方法中的currentCache就是ms.getCache()获取的Cache对象。关于curretnCache设置则是在如下的方法中:

public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
Properties props) {
	typeClass = valueOrDefault(typeClass, PerpetualCache.class);
	evictionClass = valueOrDefault(evictionClass, LruCache.class);
	//创建Cache对象
	Cache cache = new CacheBuilder(currentNamespace)
		.implementation(typeClass)
		.addDecorator(evictionClass)
		.clearInterval(flushInterval)
		.size(size)
		.readWrite(readWrite)
		.properties(props)
		.build();
	//添加到Configuration
	configuration.addCache(cache);
	//设置currentCache
	currentCache = cache;
	return cache;
}

关于什么时候会设置Cache就不详细的说明了。在配置了<cache>或@CacheNamespace注解的情况下会去设置Cache对象。可以参考Mybatis源码分析(3) -- Mapper文件解析

对于ms.isUseCache()和ms.isFlushCacheRequired()的说明,我再多说几句。select语句userCache=true,flushCache=false,对于insert、update、delete语句userCache=false,flushCache=true。

最后,整理一下CachingExecutor的思路:

Cache为空

也就是没有设置<cache>或@CacheNamespace时,CachingExecutor会按照delegate来执行,相当于没有被包装处理。

Cache不为空

调用flushCacheIfRequired,检测是flushCache,如果为true则设置dirty=true,很明显对于insert、update、delete语句都不会使用缓存功能。所以说CachingExecutor是针对处理select语句的。

猜你喜欢

转载自my.oschina.net/xiaoqiyiye/blog/1625430