Mybatis之Mapper呼叫原始碼分析
Mybatis之Mapper呼叫原始碼分析
這一篇是承接前面兩篇的,分別為:Mybatis原始碼解析之配置載入(一), Mybatis原始碼解析之配置載入(二),前面兩篇講了在Mybatis啟動時如何載入配置,這一節就講在執行時,如何通過session獲取Mapper代理類,從而實現對資料庫的查詢操作。
程式
首先展示下之前寫的程式,main程式在第一篇中有展示,這裡就不再完全的貼出來了,獲取session並獲取mapper類程式碼如下:
SqlSession session = sessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
//執行查詢返回一個唯一user物件的sql
User user = userMapper.getUser(1);
System.out.println(user);
獲取session在這就不講了,就是將configuration中的配置資訊取出,然後例項化對應Executor類以及事務等相關資訊,這裡就直接從session中獲取mapper類開始說。
session物件呼叫getMapper方法獲取Mapper代理類,我們進入到getMapper方法。
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
在第三個getMapper方法中,從knownMappers中取出對應代理工廠類,knownMappers是一個HashMap物件,從第二篇文章中的addMapper方法得知,在載入mapper介面時,每一個Mapper介面類都被新增進knownMappers中,具體可以看以下一段程式碼,
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
繼續回到之前的程式碼,在獲取到MapperProxyFactory物件後,進行了例項化操作,繼續分析,此處進入到newInstance方法中。
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
此處根據mapperInterface獲取到Mapper的代理類物件,就是MapperProxy類。
當我們的演示程式main程式開始呼叫mapper中的方法時,即如下程式碼:
//執行查詢返回一個唯一user物件的sql
User user = userMapper.getUser(1);
對應我們轉到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);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
我們直接看最下面的mapperMethod.execute(sqlSession, args),進入到execute方法中。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
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;
}
我們的演示程式中呼叫的方法為SELECT型別,同時我們要求返回的為resultMap,所以應該是最後一種方法呼叫,即else中的
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
其中的Object param = method.convertArgsToSqlCommandParam(args)
是將引數轉換成kv結構的map返回,這裡面就不再多看,直接到selectOne方法中。
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
轉到了session中的selectOne方法,session中的selectOne還是呼叫了selectList方法,繼續轉到selectList方法。
@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 {
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
selectList最終呼叫了**MappedStatement ms =configuration.getMappedStatement(statement);從之前的文章分析得知此處getMappedStatement實際上就是從mappedStatements.get(id)**中取出對應SQL,這裡傳進去的statement就是方法名,也就是id,因此此處也與前面串聯起來。
接下來就是Executor執行sql的過程,我們進入到query方法中。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
根據parameter獲取到BoundSql,繼續找query方法。
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++;
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;
}
這個query方法中只看list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql)這一行,其他的在日後分析cache的時候再回來繼續看原始碼。
我們轉到queryFromDatabase方法。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
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;
}
轉向doQuery()方法,當然doQuery有多個實現類,這裡簡單點直接看SimpleExecutor,simpleExecutor中的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);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
這裡的stmt = prepareStatement(handler, ms.getStatementLog());也不再詳細講述了,就是對引數的組裝,以後講其他的在此會涉及到,以後再說,直接看query方法。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
上述程式碼中最終是到了Statemet進行execute查詢方法,而Statement物件即為java.sql包下的Statement物件,最終還是轉換成了Statement物件查詢,然後將返回結果進行了處理,從而返回一個list結果,然後有了上述程式碼中的selectOne方法的size判斷。
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
如果返回多條則報錯,如果返回一條則直接返回結果。
Mybatis呼叫方法原始碼分析就到這了,其中漏了很多重要的步驟的講述,比如plugin的使用,引數的組裝等等,這個到以後分析其他的時候再來講述。