在上一篇博文中,已经分析了Mybatis事务相关的内容,而今天的这篇博文,很多内容都是在方法method内部,与事务无关,所以,建议暂时忘记事务概念,避免搅扰。
Mybatis对数据库的操作,都将委托给执行器Executor来完成,所以,在Mybatis这部电影当中,Executor是绝对的领衔主演。
在Mybatis中,SqlSession对数据库的操作,将委托给执行器Executor来完成,而Executor由五鼠组成,分别是:简单鼠SimpleExecutor、重用鼠ReuseExecutor、批量鼠BatchExecutor、缓存鼠CachingExecutor、无用鼠ClosedExecutor。
1. Executor接口设计与类结构图
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
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;
<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 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();
void setExecutorWrapper(Executor executor);
}
这些接口方法相对都比较见名知意,但其中的flushStatements()方法,给人很多疑惑,它是我们关注的重点方法。
类结构图:
(Made In Intellij Idea IDE)
不是说有五鼠吗?怎么就看见四鼠?其实,还有一个无用鼠ClosedExecutor是静态内部类:private static final class ClosedExecutor extends BaseExecutor。
接下来,我们看看这五鼠都有哪些本领,能闹得起东京。
简单鼠SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)
重用鼠ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。(可以是Statement或PrepareStatement对象)
批量鼠BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或PrepareStatement对象)
缓存鼠CachingExecutor:装饰设计模式典范,先从缓存中获取查询结果,存在就返回,不存在,再委托给Executor delegate去数据库取,delegate可以是上面任一的SimpleExecutor、ReuseExecutor、BatchExecutor。
无用鼠ClosedExecutor:毫无用处,读者可自行查看其源码,仅作为一种标识,和Serializable标记接口作用相当。
作用范围:以上这五鼠的作用范围,都严格限制在SqlSession生命周期范围内。
2. 基类BaseExecutor源码解析
org.apache.ibatis.executor.BaseExecutor.java部分源码。
@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.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
protected abstract int doUpdate(MappedStatement ms, Object parameter)
throws SQLException;
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;
经典的模板设计模式,不同的实现类,分别实现自己的三个抽象方法即可:doUpdate()、doQuery()、doFlushStatement()。五鼠闹东京的本领,就看各自对这三个方法的江湖修炼情况了。
2.1. 简单鼠SimpleExecutor源码解析
简单鼠SimpleExecutor,真的非常简单,几乎没啥可说的。
@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();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
随建随关Statement。
2.2. 重用鼠ReuseExecutor源码解析
public class ReuseExecutor extends BaseExecutor {
// key=sql, value=Statement,不同的sql,对应不同的Statement
private final Map<String, Statement> statementMap = new HashMap<String, Statement>();
@Override
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 stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// sql是key,不同的sql,将产生不同的Statement
if (hasStatementFor(sql)) {
// 从statementMap中获取Statement
stmt = getStatement(sql);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
// 将Statement放到statementMap中
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
// ...
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
for (Statement stmt : statementMap.values()) {
closeStatement(stmt);
}
statementMap.clear();
return Collections.emptyList();
}
重用鼠ReuseExecutor就是依赖Map<String, Statement>来完成对Statement的重用的(用完不关)。
总不能一直不关吧?到底什么时候关闭这些Statement对象的?问的非常好。
方法flushStatements()就是用来处理这些Statement对象的。
在执行commit、rollback等动作前,将会执行flushStatements()方法,将Statement对象逐一关闭。读者可参看BaseExecutor源码。
2.3. 批量鼠BatchExecutor原理及源码解析(重点难点)
批量鼠BatchExecutor是我们重点分析的对象,也是我们重点学习的对象,因为它略微难一点点,不过,我会使出我的九阳神功,将它描述的简单易懂。
// 缓存多个Statement对象,每个Statement都是addBatch()后,等待执行
private final List<Statement> statementList = new ArrayList<Statement>();
// 对应的结果集(主要保存了update结果的count数量)
private final List<BatchResult> batchResultList = new ArrayList<BatchResult>();
// 当前保存的sql,即上次执行的sql
private String currentSql;
@Override
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();
// 本次执行的sql
final String sql = boundSql.getSql();
final Statement stmt;
// 要求当前的sql和上一次的currentSql相同,同时MappedStatement也必须相同
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
// 已经存在Statement,取出最后一个Statement,有序
int last = statementList.size() - 1;
stmt = statementList.get(last);
handler.parameterize(stmt);//fix Issues 322
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
// 尚不存在,新建Statement
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection);
handler.parameterize(stmt); //fix Issues 322
currentSql = sql;
currentStatement = ms;
// 放到Statement缓存
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// 将sql以addBatch()的方式,添加到Statement中(该步骤由StatementHandler内部完成)
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
需要注意的是sql.equals(currentSql)和statementList.get(last),充分说明了其有序逻辑:AABB,将生成2个Statement对象;AABBAA,将生成3个Statement对象,而不是2个。因为,只要sql有变化,将导致生成新的Statement对象。
缓存了这么多Statement批处理对象,何时执行它们?在doFlushStatements()方法中完成执行stmt.executeBatch(),随即关闭这些Statement对象。读者可自行查看。
这里所说的Statement,可以是Statement或Preparestatement。
注:对于批处理来说,JDBC只支持update操作(update、insert、delete等),不支持select查询操作。
BatchExecutor和JDBC批处理的区别。
JDBC中Statement的批处理原理图。
(Made In Windows 画图板)
对于Statement来说,只要SQL不同,就会产生新编译动作,Statement不支持问号“?”参数占位符。
JDBC中PrepareStatement的批处理原理图。
(Made In Windows 画图板)
对于PrepareStatement,只要SQL相同,就只会编译一次,如果SQL不同呢?此时和Statement一样,会编译多次。PrepareStatement的优势在于支持问号“?”参数占位符,SQL相同,参数不同时,可以减少编译次数至一次,大大提高效率;另外可以防止SQL注入漏洞。
BatchExecutor的批处理原理图。
(Made In Windows 画图板)
BatchExecutor的批处理,和JDBC的批处理,主要区别就是BatchExecutor维护了一组Statement批处理对象,它有自动路由功能,SQL1、SQL2、SQL3代表不同的SQL。(Statement或Preparestatement)
2.4. 缓存鼠CachingExecutor原理及源码分析
缓存鼠CachingExecutor使用了经典的装饰器设计模式,先从缓存中取查询结果,有则返回,如果没有,再委托给Executor delegate从数据库中查询。
private Executor delegate;
delegate可以是上面任一的SimpleExecutor、ReuseExecutor、BatchExecutor。
比较简单,没什么可说的,有关Mybatis的缓存原理,将单独开启一篇缓存原理的文章。
2.5. 无用鼠ClosedExecutor
无用鼠ClosedExecutor,无内容可介绍,它仅作为一种标识,和Serializable标记接口作用相当。
3. Executor的创建时机和创建策略
Executor的创建时机是,创建DefaultSqlSession实例时,作为构造参数传递进去。(见DefaultSqlSessionFactory.java内)
Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
Executor有三种创建策略。
public enum ExecutorType {
SIMPLE, REUSE, BATCH
}
那么,Mybatis怎么知道,应该创建这三种策略中的哪一种呢?依据是什么?
有两种手段来指定Executor创建策略。
第一种,configuration配置文件中,配置默认ExecutorType类型。(当不配置时,默认为ExecutorType.SIMPLE)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="defaultExecutorType" value="REUSE" />
</settings>
</configuration>
第二种,手动给DefaultSqlSessionFactory.java的创建SqlSession的方法传递ExecutorType参数。
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return openSessionFromDataSource(execType, null, autoCommit);
}
至此,关于Executor从哪儿来,要到哪里去,能干什么,都已经打听清楚了。
这五鼠,义结金兰,七侠和五义流传在民间。
版权提示:文章出自开源中国社区,若对文章感兴趣,可关注我的开源中国社区博客(http://my.oschina.net/zudajun)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)
来源:oschina
链接:https://my.oschina.net/u/2727738/blog/667214