//1.通過輸入流解析xml配置文件
InputStream inputstream = Resources.getResourceAsStream("xxx.xml")
SqlsessionFactory sqlsessionfactory = new SqlsessionFactoryBuilder().build(inputstream);
//2.獲取和數據庫的鏈接,創建會話
SqlSession openSession = sqlsessionfactory.openSession();
//3.獲取接口的實現類對象,會爲接口自動的創建一個代理對象mapper,代理對象會去執行增刪改查方法
xxxMapper mapper = openSession.getMapper(xxxMapper.class)
//4.執行增刪改的方法
mybatis源碼解析-SqlsessionFactory
mybatis源碼解析-獲取Sqlsession
mybatis源碼解析-getMapper
一個mybatis運行前的步驟我們都分析的差不多了,現在來看一下具體的執行過程
查詢如何實現
我們假如有一個方法叫做getXXXById(),我們便順理成章的進入了這個方法內部,我們之前提到過,openSession.getMapper(xxxMapper.class)
返回的是MapperProxy
對象,也就是mapper的代理對象,我們看下代碼:
1.1
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
首先這個invoke
方法會先做判斷:if (Object.class.equals(method.getDeclaringClass()))
它的意思是判斷通過代理對象執行的方法是不是Object方法,或者是不是mapper接口中的方法,如果是Object的方法比如toString就直接執行下面的代碼了。入如果是mapper接口中的方法,則會將當前方法通過MapperMethod mapperMethod = this.cachedMapperMethod(method);
封裝成MapperMethod
,然後通過mapperMethod.execute(this.sqlSession, args);
來執行,這個參數中的sqlsession是我們MapperProxy
封裝好了的,Object[] args
是我們查詢方法的參數,我們來看一下MapperMethod這個類:
1.2
public class MapperMethod {
private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
這是這個類的基本信息,其中MapperMethod.SqlCommand command
是這個類中的靜態內部類,在構造方法中就進行了解析,目的是判斷要運行的方法是增刪改查的那種類型,然後看這個類中的execute方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
我們要運行的是根據id查詢對象,所以是select
類型的,進入了select之後會根據返回值類型進行判斷,我們的情況都不滿足,直接進入else語句,這裏面的method.convertArgsToSqlCommandParam(args)
是將Object類型的參數轉爲我們需要的類型,我們不妨細看一下:
看到這裏是不是很熟悉,和mybatis參數處理過程及原理裏面講得一樣,這裏就不多說了,參數轉換完成後,就會執行sqlSession.selectOne(this.command.getName(), param);
這個方法,其中參數this.command.getName() 就是我們在mapper.xml中寫的select標籤的唯一標識,即方法所在接口的全限定類名,我們進入這個selectOne方法:
1.3這個方法是DefaultSqlSession實現的
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.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;
}
}
我們發現,無論是查詢單個還是多個,都調用了selectList
方法,我們來看一看這個方法,參數statement是我們對應mapper接口中的一個方法,有唯一標識,parameter是方法的參數
1.4
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
這裏需要注意了,MappedStatement ms = this.configuration.getMappedStatement(statement); 就是根據configuration全局配置對象來獲取之前被封裝的MappedStatement
對象,而這個對象正是封裝了增刪改查標籤中的信息,包括我們配置的SQL,SQL的id,緩存信息,resultMap等,每個標籤都有一個該對象封裝,這個在本票文章開始的mybatis源碼解析-SqlsessionFactory有詳細介紹,每個增刪改查標籤都有一個封裝它的MappedStatement對象,他們通過map封裝,這裏的selectList
方法通過唯一標識statement找到了對應的接口方法
我們接着往下走,
完成了MappedStatement
的查詢,會將這個作爲參數傳遞給this.executor.query
方法,我們來看一下這個方法:其中有個參數this.wrapCollection(parameter)
,我們看一下這個封裝參數的方法:
private Object wrapCollection(Object object) {
DefaultSqlSession.StrictMap map;
if (object instanceof Collection) {
map = new DefaultSqlSession.StrictMap();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
map = new DefaultSqlSession.StrictMap();
map.put("array", object);
return map;
} else {
return object;
}
}
是不是有一種豁然開朗的感覺?是的,就是這段代碼的作用我們纔可以將List類型的集合封裝爲list或者是collection,好了,現在跳過這段代碼,接着往下走,看query方法,注意了,這裏的this.executor.query是我們在獲取SqlSession步驟中創建Executor時的Executor子類CachingExecutor
:
1.5
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
注意第一句的BoundSql
,我們進入ms.getBoundSql
看一下:
這個類提供三個主要的屬性:sql,parameterMappings,parameterObject,我們這裏簡單介紹一下:
- sql就是我們卸載增刪改標籤裏的sql語句
- parameterObject就是我們寫在接口裏方法的參數
- parameterMappings,它是一個
List
,它的每一個參數都是ParameterMapping對象,這個對象會描述我們的參數,包括屬性,名稱,表達式,javatype等重要信息,我們一般不需要去改變它,通過它可以實現參數和SQL的結合
回到query方法,裏面還有一個二級緩存key:CacheKey key
,這裏就不多說它了,然後它再次調用本類中的另一個重載的query方法:
1.6
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) {
this.flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, boundSql);
List<E> list = (List)this.tcm.getObject(cache, key);
if (list == null) {
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}
return list;
}
}
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
在之前介紹獲取sqlsession的文章中我們提到,通過openSession創建的Executor也就是executor = new SimpleExecutor(this, transaction);
這段代碼被executor = new CachingExecutor((Executor)executor);
封裝了,真正起作用的是CachingExecutor
類中的delegate
,如果沒有緩存,這個執行器進行查詢this.delegate.query
,我們進入這個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 (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if (this.queryStack == 0) {
Iterator var8 = this.deferredLoads.iterator();
while(var8.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
我們發現會執行**list = resultHandler == null ? (List)this.localCache.getObject(key) : null;**這行代碼,這是執行了二級緩存之後再執行的本地緩存,這也應證了mybatis會先查二級,再查一級緩存,如果一級二級都爲null
,就會執行queryFromDatabase
方法,我們進入這個方法:
1.7
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
這個方法會現在本地緩存中設置一個key,再查出數據放進本地緩存,我們看doQuery
方法,這個方法的參數我們都很熟悉了,直接進入該方法了
1.8
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
這個方法就很有意思了,裏面有Statement
原生JDBC,而且出現了StatementHandler
,這是SqlSession下的一個重要的對象,我們之前說的Executor也是一個,它的作用是創建出statement(prepareStatement)對象,起到承上啓下的作用,我們進入這個方法看一下:
1.9
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
我們在mapper中寫select標籤時有一個屬性:
默認採取預編譯模式,那麼在這個方法中就會創建PreparedStatementHandler
它是StatementHandler
的子類