Trong quá trình phân tích, chúng ta đã biết rằng việc thực thi SQL cụ thể được thực hiện thông qua các phương thức tương ứng của giao diện SqlSession. Cuối cùng, SqlSession sẽ gọi đến đối tượng Executor của chính nó để xử lý query và update. Bài viết này sẽ phân tích chi tiết về Executor.
Executor đóng vai trò là công cụ thực thi SQL trong MyBatis. Trong khi SqlSession hướng tới chương trình, Executor lại trực tiếp làm việc với cơ sở dữ liệu. Dưới đây là các phương thức có sẵn trong giao diện Executor:
public interface Executor {
int execute(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> fetch(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> fetchCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List<BatchResult> flush() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey generateCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds);
boolean hasCache(MappedStatement ms, CacheKey key);
void clearCache();
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
}
Tương tự như SqlSession, Executor cũng định nghĩa nhiều phương thức liên quan đến việc thực thi SQL, bao gồm các phương thức query, update, commit và rollback. Để hiểu rõ hơn, ta sẽ xem xét cách thức hoạt động của phương thức fetch.
Giao diện Executor có hai lớp triển khai chính là BaseExecutor và CachingExecutor. CachingExecutor chịu trách nhiệm quản lý bộ nhớ đệm và sẽ được thảo luận sau. Hiện tại, chúng ta sẽ tập trung vào BaseExecutor.
BaseExecutor có các thuộc tính sau:
protected Transaction transaction; // Quản lý giao dịch
protected Executor wrapper; // Đối tượng bọc Executor
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads; // Hàng đợi an toàn luồng
protected PerpetualCache localCache; // Bộ nhớ đệm cục bộ
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryCount = 0; // Số lần truy vấn
private boolean closed; // Trạng thái đóng/mở
Đây là mã nguồn của phương thức fetch trong BaseExecutor:
public <E> List<E> fetch(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("fetching data").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor đã bị đóng.");
}
if (queryCount == 0 && ms.isFlushCacheRequired()) {
clearCache();
}
List<E> resultList;
try {
queryCount++;
resultList = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (resultList != null) {
handleLocallyCachedParameters(ms, key, parameter);
} else {
resultList = fetchFromDatabase(ms, parameter, rowBounds, resultHandler, key);
}
} finally {
queryCount--;
}
if (queryCount == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearCache();
}
}
return resultList;
}
Có thể thấy rằng BaseExecutor ưu tiên tìm kiếm dữ liệu từ bộ nhớ đệm trước khi truy vấn trực tiếp vào cơ sở dữ liệu. Một điểm đáng chú ý là biến queryCount được tăng và giảm nhằm đảm bảo logic xử lý đúng đắn.
Tiếp theo, hãy xem cách BaseExecutor lấy dữ liệu từ cơ sở dữ liệu nếu không tìm thấy trong bộ nhớ đệm:
private <E> List<E> fetchFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key) throws SQLException {
List<E> resultList;
localCache.putObject(key, PLACEHOLDER);
try {
resultList = performFetch(ms, parameter, rowBounds, resultHandler);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, resultList);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return resultList;
}
Phương thức này sử dụng performFetch để truy vấn dữ liệu từ cơ sở dữ liệu và lưu kết quả vào bộ nhớ đệm. Phương thức performFetch là một phương thức trừu tượng được triển khai bởi các lớp con của BaseExecutor.
Dưới đây là các phương thức trừu tượng trong BaseExecutor:
protected abstract int performUpdate(MappedStatement ms, Object parameter) throws SQLException;
protected abstract List<BatchResult> performFlush(boolean isRollback) throws SQLException;
protected abstract <E> List<E> performFetch(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
protected abstract <E> Cursor<E> performFetchCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
protected void closeResource(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
// Bỏ qua lỗi
}
}
}
Lớp BaseExecutor có bốn lớp con chính: SimpleExecutor, ReuseExecutor, BatchExecutor và ClosedExecutor. Chi tiết về từng lớp sẽ được phân tích sâu hơn trong phần tiếp theo.
Tóm lại, SqlSession sử dụng các phương thức của Executor để thực thi SQL. Executor mặc định được cài đặt là BaseExecutor, và BaseExecutor lại gọi các phương thức từ các lớp con của nó để hoàn thành công việc. Ngoài ra, BaseExecutor còn xử lý việc lưu trữ kết quả truy vấn vào bộ nhớ đệm.