注:この一連のソースコード分析は、mybatis 3.5.6、ソースコードgitee倉庫倉庫アドレス:funcy/mybatisに基づいています。
mybatis
sqlステートメントを実行する操作はExecutor
、エグゼキューター()によって完了されます。エグゼキューターは次のmybatis
3つのタイプを提供しますExecutor
。
タイプ | 名前 | 関数 |
---|---|---|
REUSE |
エグゼキュータを再利用 | キャッシュされ、同じものをPreparedStatement 次の実行sql に再利用できます |
BATCH |
バッチエグゼキュータ | 変更操作をローカルに記録し、次のクエリが発生したときにプログラムが変更操作をトリガーするか、バッチで実行するのを待ちます |
SIMPLE |
シンプルなエグゼキュータ | 実行ごとに生成され、実行PreparedStatement 後に閉じられ、キャッシュされません |
さらに、キャッシュ関連の操作を処理するための上記の3つのエグゼキュータの装飾クラスでmybatis
あるキャッシュエグゼキュータも提供されCachingExecutor
ます。実際の作業は、上記の3つのエグゼキュータの1つです。
Executor
継続構造は次のとおりです。
1.1。BaseExecutor
BaseExecutor
Executor
次のような基本的な操作を実装しました。
- トランザクション処理:
commit(...)
:トランザクションのコミットを処理しますrollback(...)
:トランザクションのロールバックを処理します
- キャッシュ処理:
createCacheKey(...)
:キャッシュキーを作成するclearLocalCache(...)
:キャッシュの消去
- カード操作:
query(...)
:クエリ操作update(...)
:更新操作、挿入、削除もここで処理されます
- サブクラスの実装に任せた
doUpdate(...)
:特定の更新操作は、実装するサブクラスに任されていますdoQuery(...)
:特定のクエリ操作は、実装するサブクラスに任されています
次に実装に焦点を当てるときは、実装Executor
するサブクラスに任されているメソッドにのみ焦点を当てます。
2.2。SimpleExecutor
SimpleExecutor
実行ごとに生成され、実行PreparedStatement
後に閉じられ、キャッシュされません。実装方法を見てみましょう。その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 handler = configuration.newStatementHandler(wrapper, ms, parameter,
rowBounds, resultHandler, boundSql);
// 得到 PrepareStatement
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询
return handler.query(stmt, resultHandler);
} finally {
// 关闭 Statement
closeStatement(stmt);
}
}
复制代码
Statement
それを取得する方法は次のSimpleExecutor#prepareStatement
とおりです。
private Statement prepareStatement(StatementHandler handler, Log statementLog)
throws SQLException {
Statement stmt;
// 获取数据库连接
Connection connection = getConnection(statementLog);
// 获取 Statement
stmt = handler.prepare(connection, transaction.getTimeout());
// 处理参数设置
handler.parameterize(stmt);
return stmt;
}
复制代码
このメソッドは、最初にデータベース接続を取得し、次にを取得Statement
してから、パラメーター設定を処理します。
关于数据库连接的获取,我们在分析配置文件的解析时,数据源的配置最终会转化成PooledDataSource
或UnpooledDataSource
对象,数据库连接就是从数据源来的。
至于Statement
的生成,PreparedStatement
的实例化操作方法为PreparedStatementHandler#instantiateStatement
,这些都是常规的jdbc操作,就不细看了。
处理sql的执行方法为PreparedStatementHandler#query
:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
复制代码
SimpleExecutor#doQuery(...)
的执行流程如下:
- 获取数据库连接
- 获取
PrepareStatement
- 执行查询
- 关闭
PrepareStatement
SimpleExecutor
的操作就是常规的jdbc操作。
3. ReuseExecutor
ReuseExecutor
会缓存PreparedStatement
,下一次执行相同的sql
可重用。
我们依然分析doQuery(...)
方法:
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(wrapper, ms, parameter,
rowBounds, resultHandler, boundSql);
// 获取 Statement
Statement stmt = prepareStatement(handler, ms.getStatementLog());
// 处理查询操作
return handler.query(stmt, resultHandler);
}
复制代码
与SimpleExecutor
相比,ReuseExecutor
的doQuery(...)
方法并没关闭Statement
.我们来看看Statement
的获取操作:
private Statement prepareStatement(StatementHandler handler, Log statementLog)
throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// 根据sql语句判断是否有Statement缓存
if (hasStatementFor(sql)) {
// 有缓存,直接使用
stmt = getStatement(sql);
applyTransactionTimeout(stmt);
} else {
// 没缓存,获取数据库连接,再获取 Statement
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
// 缓存 Statement
putStatement(sql, stmt);
}
// 处理参数
handler.parameterize(stmt);
return stmt;
}
复制代码
可以看到,ReuseExecutor
获取Statement
时,会先从缓存里获取,缓存里没有才会新建一个Statement
,然后将新建的Statement
添加到缓存中。从这里可以看出,ReuseExecutor
的Reuse,复用的是Statement
。
我们再来看看缓存Statement
的结构:
public class ReuseExecutor extends BaseExecutor {
private final Map<String, Statement> statementMap = new HashMap<>();
...
private Statement getStatement(String s) {
return statementMap.get(s);
}
private void putStatement(String sql, Statement stmt) {
statementMap.put(sql, stmt);
}
}
复制代码
由些可见,缓存Statement
的是一个Map
,key
为sql
语句,value
为Statement
.
4. BatchExecutor
BatchExecutor
会将修改操作记录在本地,等待程序触发或有下一次查询时才批量执行修改操作,即:
- 进行修改操作(
insert
,update
,delete
)时,并不会立即执行,而是会缓存到本地 - 进行查询操作(
select
)时,会先处理缓存到本地的修改操作,再进行查询操作 - 也可行触发修改操作
从以上内容来看,这种方式似乎有大坑,列举几点如下:
- 修改操作缓存到本地后,如果执行前遇到意外重启,缓存的记录会不会丢失?
- 分布式环境下,多机共同协作,更新在A机上执行,查询在B机上执行,B机是不是不能查到B机的更新记录(B机的更新操作还在缓存中,并未执行)?
我们来看下BatchExecutor
的更新操作,进入doUpdate(...)
方法:
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;
// 如果传入的sql是当前保存的 sql,直接使用
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
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, transaction.getTimeout());
handler.parameterize(stmt); // fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// 保存,等待之后批量执行
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
复制代码
BatchExecutor
有成员变量会记录上一次执行的sql
与MappedStatement
,如果本次执行的sql
与MappedStatement
与上一次执行的相同,则直接使用上一次的Statement
,否则就新建连接、获取Statement
.
得到Statement
后,会调用PreparedStatementHandler#batch
方法:
public void batch(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.addBatch();
}
复制代码
这个方法并没有执行,只是调用PreparedStatement#addBatch
方法,将当前statement
保存了起来。
PreparedStatement#addBatch
方法如何使用呢?简单示意下:
// 获取连接
Connection connection = getConnection();
// 预编译sql
String sql = "xxx";
PreparedStatement statement = connection.prepareStatement(sql);
//记录1
statement.setInt(1, 1);
statement.setString(2, "one");
statement.addBatch();
//记录2
statement.setInt(1, 2);
statement.setString(2, "two");
statement.addBatch();
//记录3
statement.setInt(1, 3);
statement.setString(2, "three");
statement.addBatch();
//批量执行
int[] counts = statement.executeBatch();
// 关闭statment,关闭连接
...
复制代码
BatchExecutor
的doUpdate(...)
方法并没有执行sql语句,我们再来看看doQuery(...)
方法:
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 处理缓存中的 statements
flushStatements();
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject,
rowBounds, resultHandler, boundSql);
// 获取连接,获取Statement,处理参数
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
// 执行查询
return handler.query(stmt, resultHandler);
} finally {
// 关闭 Statement
closeStatement(stmt);
}
}
复制代码
doQuery(...)
方法会先调用flushStatements()
方法,然后再处理查询操作,整个过程基本同SimpleExecutor
一致,即"获取数据库连接-获取Statement
-处理查询-关闭Statement
"等几步。我们重点来看flushStatements()
方法的流程.
flushStatements()
方法最终调用的是BatchExecutor#doFlushStatements
方法,代码如下:
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
List<BatchResult> results = new ArrayList<>();
if (isRollback) {
return Collections.emptyList();
}
// 遍历的statementList,statementList就是缓存statement的结构
for (int i = 0, n = statementList.size(); i < n; i++) {
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
// 关键代码:stmt.executeBatch(),批量执行sql
batchResult.setUpdateCounts(stmt.executeBatch());
...
} catch (BatchUpdateException e) {
...
}
results.add(batchResult);
}
return results;
} finally {
...
}
}
复制代码
BatchExecutor#doFlushStatements
方法的关键代码就是batchResult.setUpdateCounts(stmt.executeBatch());
了 ,其中的stmt.executeBatch()
就是批量执行更新操作了。
上記の分析から、BatchExecutor#doUpdate(...)
メソッドはSQLステートメントを実行せず、SQLステートメントを変換しStatement
てキャッシュするだけであることがわかります。メソッドを実行するときはBatchExecutor#doQuery(...)
、最初Statement
にキャッシュが実行され、次にクエリ操作が実行されます。もちろん、メソッドを手動で呼び出しBatchExecutor#flushStatements
てキャッシュを実行することもできStatement
ます。
5.5。CachingExecutor
CachingExecutor
上記の3種類のエグゼキュータとは異なり、キャッシュからデータを取得できる装飾クラスであり、実際の作業は上記の3種類のエグゼキュータの1つです。
public class CachingExecutor implements Executor {
// 具体的执行器
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
...
}
复制代码
コードの観点からは、これはのサブクラスであり、コンストラクターによって型が渡されるExecutor
メンバー変数があります。つまり、作成時に、上記の3つのエグゼキュータの1つが渡され、メンバー変数に保存されます。delegate
Executor
CachingExecutor
CachingExecutor
delegate
CachingExecutor
方法は次のquery(...)
とおりです。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建缓存key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
// 操作缓存
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 从缓存中获取
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 实际处理查询的操作
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 添加到缓存中
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
复制代码
コードの観点から、CachingExecutor
クエリを処理するときは最初にキャッシュから取得され、キャッシュに存在しない場合query(xxx)
は特定のエグゼキュータのメソッドが実行されます。
この記事の元のテキストへのリンク:my.oschina.net/funcy/blog/…、著者の個人的なレベルに限定されており、テキストには必然的に間違いがあります。私を訂正することを歓迎します!オリジナリティは簡単ではありません。商用の再版の場合は、著者に許可を求めてください。非商用の再版の場合は、出典を示してください。