本文基于mybatis-spring 1.3.1和mybatis 3.4.4版本
SqlSession执行增删改查都是委托给Executor完成的。
Executor主要完成以下几项内容:
- 处理缓存,包括一级缓存和二级缓存
- 获取数据库连接
- 创建Statement或者PrepareStatement对象
- 访问数据库执行SQL语句
- 处理数据库返回结果
Executor继承结构如下:
接下来本文详细介绍各个实现类。
一、CachingExecutor
CachingExecutor用于处理二级缓存,如果缓存中不存在要查询的数据,那么将查询请求委托给其他的Executor。如果是执行SQL的增删改,那么CachingExecutor将清空二级缓存。
关于CachingExecutor的其他内容可以参见《Mybatis解析-缓存原理解析》。
二、BaseExecutor
BaseExecutor是除CachingExecutor之外,其他Executor实现类的基类。该类主要处理一级缓存。该类中属性localCache表示一级缓存。
protected PerpetualCache localCache;
当调用该类的查询方法时,先查看一级缓存中是否已经有数据,如果有则直接从缓存获取,如果没有调用子类的查询方法从数据库中获取。
当调用该类的update方法(mybatis将delete和insert认为是update,统一调用该类update方法)时,BaseExecutor将一级缓存清空,然后调用子类对应的增删改方法。
调用执行rollback/commit方法时,该类清空一级缓存。
三、SimpleExecutor
SimpleExecutor继承自BaseExecutor,该类比较简单。
当执行增删改查时,该类获取数据库连接,创建PrepareStatement或者Statement对象,执行SQL语句,最后将数据库返回结果转化为设定的对象。下面以doQuery方法为例:
//父类执行查询时调用该方法
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对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//获得数据库连接,创建Statement或者PrepareStatement
stmt = prepareStatement(handler, ms.getStatementLog());
//执行SQL语句,将数据库返回结果转化为设定的对象,比如List,Map或者是POJO
return handler.<E>query(stmt, resultHandler);
} finally {
//关闭Statement对象
closeStatement(stmt);
}
}
SimpleExecutor基本是按照标注JDBC流程执行SQL语句获得返回结果。
四、BatchExecutor
当执行查询时,BatchExecutor与SimpleExecutor的处理逻辑是一样的。不同的是执行更新方法(mybatis认为delete和insert都是update)。执行更新方法时,BatchExecutor不是直接执行SQL语句,而是将其放到批次里面,等到提交的时候一起执行。
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
//创建Statement或者PreparedStatement对象
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是否在上一次已经执行过
//下面的条件表示,SQL语句相同,调用的mapper接口方法也相同
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
//如果在上一次已经执行过,那么直接复用上一次使用的Statement或者PreparedStatement对象
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
handler.parameterize(stmt);
//batchResult用于记录批次执行结果
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
//下面的内容是新建Statement或者PreparedStatement对象,用于执行新的SQL语句
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
//将SQL加入批次
handler.batch(stmt);
//返回一个常量值,表示当前是批次执行
return BATCH_UPDATE_RETURN_VALUE;
}
当连续执行的SQL语句相同时,BatchExecutor才会将其加入到同一个批次中,否则新建Statement或者PreparedStatement对象,并创建新批次。
当提交事务时,会执行BatchExecutor的doFlushStatements方法,
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
List<BatchResult> results = new ArrayList<>();
if (isRollback) {
return Collections.emptyList();
}
//遍历每个批次
for (int i = 0, n = statementList.size(); i < n; i++) {
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
//执行每个批次,将批次执行结果存入BatchResult对象
batchResult.setUpdateCounts(stmt.executeBatch());
//删减代码
closeStatement(stmt);
} catch (BatchUpdateException e) {
//代码删减
return results;
} finally {
for (Statement stmt : statementList) {
//关闭每个Statement对象
closeStatement(stmt);
}
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
五、ReuseExecutor
ReuseExecutor从名字可以看出该类主要特点是复用。它复用的是Statement对象或者PreparedStatement对象。
该类中有一个属性statementMap,如下面代码所示,key是SQL语句,value是Statement对象。每次执行首先根据SQL语句查询statementMap,如果有对应的Statement对象,则直接使用该Statement对象,如果没有,则创建新的Statement对象,然后将其和SQL语句添加到statementMap,以备下次使用。
private final Map<String, Statement> statementMap = new HashMap<>();
下面来一下具体代码实现。
//增删改操作
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Configuration configuration = ms.getConfiguration();
//创建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
//创建或者复用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对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//创建或者复用Statement对象
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
//得到原始SQL语句
String sql = boundSql.getSql();
//检查该SQL是否在statementMap中存在
if (hasStatementFor(sql)) {
//得到已经存在的Statement对象
stmt = getStatement(sql);
//设置超时参数
applyTransactionTimeout(stmt);
} else {
//如果之前没有执行过该SQL,那么获取连接
Connection connection = getConnection(statementLog);
//创建新的Statement对象
stmt = handler.prepare(connection, transaction.getTimeout());
//将SQL语句和Statement对象添加到statementMap中,以备后面复用
putStatement(sql, stmt);
}
//如果stmt是PreparedStatement对象,下面的方法用于设置SQL语句的参数
handler.parameterize(stmt);
return stmt;
}
六、ClosedExecutor
ClosedExecutor是ResultLoaderMap内部类,外部无法使用,调用其方法会抛出异常。