從之前的文章,我們知道了其實mapper真正執行的方法就下面的最後兩行。(以下所有的分析都基於一次mybatis的一次select查詢,查詢的demo代碼在我的github上地址)
MapperProxy類中的invoke函數
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//獲取MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
//執行查詢並且返回結果
return mapperMethod.execute(sqlSession, args);
}
MapperMthod
private MapperMethod cachedMapperMethod(Method method) {
//methodCache其實就是一個緩存,將方法與mapperMethod作爲一組鍵值對進行緩存
MapperMethod mapperMethod = methodCache.get(method);
//若緩存中沒找到則生成一個,在把生成的mapperMethod加入緩存
if (mapperMethod == null) {
//生成mapperMethod的方法
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
這裏解釋一下mapperMethod有什麼作用,首先看一下MapperMethod類的具體參數
public class MapperMethod {
//記錄方法是什麼類型的方法(UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;)
private final SqlCommand command;
//記錄方法的具體情況(比如說返回一個還是多個,方法返回類型,方法的參數是啥等信息)
private final MethodSignature method;
//MapperMethod的構造函數
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
}
//執行方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//通過command的類型去找實際需要執行的方法,不一個個分析了,只以select的executeForMany爲例子
//其他都是差不了多少的
switch (command.getType()) {
//若是插入類型走這裏
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
//若是更新類型走這裏
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
//若是刪除類型走這裏
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
//若是選擇類型走這裏
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
//查詢方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
//
Object param = method.convertArgsToSqlCommandParam(args);
//是否需要分頁
if (method.hasRowBounds()) {
//拿到rowBounds
RowBounds rowBounds = method.extractRowBounds(args);
//查詢
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
//查詢,進入這條
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
//實際還是走的有RowBounds 的語句,只是給了默認值,默認值是拿到Integer的最大值
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//從configuration獲取MappedStatement (在初始化的時候就已經放到緩存中了,這裏只是獲取一下)
MappedStatement ms = configuration.getMappedStatement(statement);
//通過executor來查詢
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
因爲mybatis默認開始Cache,所以我們的Executor是CachingExecutor。但是我們沒在mapper.xml中配置Cache的屬性,所以最終是沒緩存功能的。通過裝飾器模式來增加了Executor的功能
Executor
//這兩個方法是CachingExecutor裏面的
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//生成BoundSql,裏面存放着sql語句
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 {
//因爲mapper中沒有配置cache,所以這裏的cahce是沒有的
Cache cache = ms.getCache();
//cache爲空,所以不會走這裏面的邏輯
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//到來這裏,通過delegate來執行query(因爲CachingExecutor是一個裝飾類,delegate是原始類(在這裏是SimpleExecutor))
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
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++;
//這裏的localCache是一級緩存,是在BaseExecutor中的
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();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//先從key中放個佔位的值
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;
}
@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,對Statement進行處理
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//生成statement
stmt = prepareStatement(handler, ms.getStatementLog());
//進行訪問
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
//這裏是prepareStatement函數,用來生成訪問數據庫需要的Statement對象
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//如果是debug狀態則對sqlSession進行代理,因爲要打印log
Connection connection = getConnection(statementLog);
//
stmt = handler.prepare(connection, transaction.getTimeout());
//對參數進行處理
handler.parameterize(stmt);
return stmt;
}
我們進handler的prepare方法看看
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//初始化statement
statement = instantiateStatement(connection);
//設置超時
setStatementTimeout(statement, transactionTimeout);
//設置fetchSize(mysql不起作用,不支持這個)
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
之後得到了Statement對象後,要去訪問數據庫了。(上面的return handler.query(stmt, resultHandler);)調用的是下面的query方法
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.<E>query(statement, resultHandler);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//將Statement強轉成PreparedStatement
PreparedStatement ps = (PreparedStatement) statement;
//對數據庫進行查詢(對數據庫進行查詢是jdbc做的事情,mybatis也只是對jdbc進行了包裝)
ps.execute();
//對返回結果進行處理
return resultSetHandler.<E> handleResultSets(ps);
}
到這裏,我們就進行了一次對數據庫的訪問,並拿到了數據。數據此時在Statement的ResultSet裏面,
如果你熟悉jdbc的話,一定不陌生下面的代碼,我們拿到resultSet後的處理方式如下。
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSet.next()) {
String string = resultSet.getString(1);
}
由於篇幅問題,mybatis對數據的處理的分析,LZ準備放在下一篇來介紹。