mybatis一個物件查詢流程簡單分析(整合spring boot)
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
首先寫一個單測,然後跟隨斷點看
@Test public void getMaxUpdateTimeByPositions(){ assert appBlockDao.getMaxUpdateTimeByPositions("mine")!=null; }
跟著斷點進來,發現進入了org.apache.ibatis.binding.MapperProxy動態代理類,執行invoke方法
//獲取一個MapperMethod MapperMethod mapperMethod = this.cachedMapperMethod(method); //接著執行execute方法,傳入一個sqlSession,與被代理方法的入參 return mapperMethod.execute(this.sqlSession, args); /** * cachedMapperMethod會從methodCache中去獲取MapperMethod,獲取不到則new一個 * 1、入參為當前被呼叫介面 private final Class<T> mapperInterface; * 2、method * 3、呼叫sqlSession.getConfiguration()介面,獲取sqlSession中的的Configuration物件,由於 * sqlSession都是從sqlSessionFactory中獲取的,所以實際上呼叫的是 * org.apache.ibatis.session.defaults.DefaultSqlSessionFactory的Configuration物件 * */ private MapperMethod cachedMapperMethod(Method method) { //private final Map<Method, MapperMethod> methodCache; MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()); this.methodCache.put(method, mapperMethod); } return mapperMethod; }
初始化MapperMethod =>org.apache.ibatis.binding.MapperMethod下的SqlCommand
private final MapperMethod.SqlCommand command; private final MapperMethod.MethodSignature method; //分別會初始化內部類SqlCommand、MethodSignature public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new MapperMethod.SqlCommand(config, mapperInterface, method); this.method = new MapperMethod.MethodSignature(config, mapperInterface, method); } //SqlCommand的例項化,篇幅有限,這裡只暫時本次被代理介面執行的重要程式碼 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { String statementName = mapperInterface.getName() + "." + method.getName(); MappedStatement ms = null; //hasStatement方法主要是呼叫protected final Map<String, MappedStatement> //mappedStatements,判斷是否存在statementName,mappedStatements主要維護的是statementName與 //xml配置檔案sql塊的關係 if (configuration.hasStatement(statementName)) { ms = configuration.getMappedStatement(statementName); } //獲取獲取不到ms,會報這個熟悉的Invalid bound statement異常 if(ms==null){ if (method.getAnnotation(Flush.class) == null) { throw new BindingException("Invalid bound statement (not found): " statementName); } } ... //獲取到ms後,給name複製未ms的id,這裡的id=我們dao介面的全類名.方法名 this.name = ms.getId(); //SqlCommandType(type=SELECT) this.type = ms.getSqlCommandType(); }
初始化MapperMethod =>org.apache.ibatis.binding.MapperMethod下的MethodSignature,主要是為MapperMethod中的一些引數賦值,為後續的流程使用,如returnType、入參params,判斷是selectOne/List等
================================================================================================
到了這裡,MapperMethod也就成功獲取到了,開始執行execute部分,select請求會進入SqlCommandType.SELECT == this.command.getType()部分
mapperMethod.execute(this.sqlSession, args);
==================================================================================
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if (SqlCommandType.INSERT == this.command.getType()) {
...
} else if (SqlCommandType.UPDATE == this.command.getType()) {
...
} else if (SqlCommandType.DELETE == this.command.getType()) {
...
//獲取剛才為SqlCommand中SqlCommandType賦值的type=SELECT
} else if (SqlCommandType.SELECT == this.command.getType()) {
if .//這裡獲取list、map等方法..{
else {
//param是一個map,存放了引數名與value
param = this.method.convertArgsToSqlCommandParam(args);
////由於我們的介面是返回Date型別,所以會執行SelectOne方法
result = sqlSession.selectOne(this.command.getName(), param);
}
} else {
...
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
sqlSession.selectOne首先經過=>
org.mybatis.spring.SqlSessionTemplate的內部類SqlSessionInterceptor的代理
通過SqlSessionUtils.getSqlSession獲取SqlSession,獲取過程=>
1、從Spring中TransactionSynchronizationManager中獲取一個sqlSessionHolder
2、接著從sqlSessionHolder獲取sqlSession,如果獲取不到,那麼就從SqlSessionFactory#openSession獲取一個sqlSession,再呼叫SqlSessionUtils#registerSessionHolder註冊到TransactionSynchronizationManager中
然後進入SqlSession的實際實現類:org.apache.ibatis.session.defaults.DefaultSqlSession,呼叫實際的selectList方法
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
//以statement為key,獲取MappedStatement,與前面講過的過程一致
MappedStatement ms = this.configuration.getMappedStatement(statement);
//執行CachingExecutor的query方法
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;
}
========================================================================================
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//dao的sql語句資訊,包括sql,parameterObject、metaParameters
BoundSql boundSql = ms.getBoundSql(parameterObject);
//獲取一級快取用的CacheKey,在BaseExecutor中建立、獲取
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
然後會執行自身的另外一個query方法,首先判斷MappedStatement中是否存在快取Cache
如果不存在,那麼會執行org.apache.ibatis.executor.BaseExecutor的query方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//內部使用一個ThreadLocal<ErrorContext>,設定resource物件等等
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;
//判斷是否從localCache中獲取resultList
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 i$ = this.deferredLoads.iterator();
while(i$.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
queryFromDatabase,會呼叫BaseExecutor子類org.apache.ibatis.executor.SimpleExecutor中的doQuery方法
1、從Configuration中獲取StatementHandler,呼叫Configuration#newStatementHandler方法,初始化org.apache.ibatis.executor.statement.RoutingStatementHandler,根據mapperStatement中的statementType判斷需要返回SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler物件
2、接著執行org.apache.ibatis.executor.SimpleExecutor中的prepareStatement方法,在這裡獲取jdbc Connection、Statement等,從而建立資料庫連結,然後呼叫StatementHandler的prepare方法,獲取Statement,呼叫org.apache.ibatis.executor.statement.BaseStatementHandler#instantiateStatement()方法,呼叫Connection.prepareStatement方法,
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
3、然後呼叫org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize()方法,設定佔位符引數PreparedStatement
4、呼叫ResultSetHandler#handleResultSets方法,然後結果集合ResultSet
5、org.apache.ibatis.executor.SimpleExecutor#closeStatement()方法,關閉org.apache.ibatis.executor.BaseExecutor#state方法,接著移除localCache,再把查詢到的結果list和key再放入localCache中
動態代理=》SqlSession=>MapperStatement=》Executor=>Handler