問題 Mybatis四大對象的創建順序? Mybatis插件的執行順序? 工程創建 環境:Mybatis(3.5.9) mybatis-demo,參考官方文檔 簡單示例 這裡只放出main方法的示例,其餘類請看demo工程。 public static void main(String[] args ...
問題
- Mybatis四大對象的創建順序?
- Mybatis插件的執行順序?
工程創建
環境:Mybatis(3.5.9)
簡單示例
這裡只放出main方法的示例,其餘類請看demo工程。
public static void main(String[] args) throws Exception {
// 配置文件路徑
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 1.讀取配置,創建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.通過工廠獲取SqlSession
SqlSession session = sqlSessionFactory.openSession();
try {
// 3.獲取mapper代理對象
StudentMapper mapper = session.getMapper(StudentMapper.class);
// 4.執行查詢,此處才真正連接資料庫
System.out.println(mapper.selectByName("張三"));
} finally {
// 5.關閉連接
session.close();
}
}
Mapper的創建
我們使用Mybatis操作資料庫,主要是通過mapper對象(在hibernate中叫dao對象)。
那麼,我們不按順序從讀取配置初始化開始講,直接看看mapper對象是如何獲取與執行的。
-
獲取mapper
// StudentMapper mapper = session.getMapper(StudentMapper.class); DefaultSqlSession.getMapper(Class<T> type) --> Configuration.getMapper(Class<T> type, SqlSession sqlSession) --> MapperRegistry.getMapper(Class<T> type, SqlSession sqlSession) --> MapperProxyFactory.newInstance(SqlSession sqlSession) --> MapperProxyFactory.newInstance(MapperProxy<T> mapperProxy)
咱們來看看MapperProxyFactory.newInstance(MapperProxy
mapperProxy)的實現 protected T newInstance(MapperProxy<T> mapperProxy) { // 可以轉換成這樣,返回的是StudentMapper的代理對象 // final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, StudentMapper.class, methodCache); // Proxy.newProxyInstance(StudentMapper.class.getClassLoader(), new Class[] { StudentMapper.class }, mapperProxy); return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
也就是說,實際返回的是
MapperProxy
對象,StudentMapper
被代理了。 -
執行mapper的方法
已知mapper對象被代理了,那麼執行mapper的所有方法,都會先經過
MapperProxy
的invoke方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 如果執行的是Object的方法,則直接執行,不繼續處理mybatis的邏輯 if (Object.class.equals(method.getDeclaringClass())) { // 舉例,如果執行的是mapper.toString(),則進入此判斷 return method.invoke(this, args); } else { // cachedInvoker(method):創建MapperMethodInvoker並緩存起來 return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }
cachedInvoker(method)返回的是
PlainMethodInvoker
,繼續進去看看// PlainMethodInvoker的方法 public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); } // MapperMethod#execute(SqlSession sqlSession, Object[] args) public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { ...... break; } case UPDATE: { ...... break; } case DELETE: { ...... break; } case SELECT: ...... break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } ...... return result; }
終於,看到了熟悉insert、update關鍵字,這裡就是具體解析執行sql,並返回結果的邏輯。咱們先略過。回去看看是如何載入配置以及生成SqlSession的。
SqlSessionFactory
SqlSessionFactory
的生成過程如下
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// xml配置解析類
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// build方法返回DefaultSqlSessionFactory
// 主要看parser.parse()
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
// 異常上下文對象,線程內共用
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
下麵來看看parser.parse()方法
// XMLConfigBuilder#parse()
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// parser.evalNode("/configuration"):獲取configuration節點
// 例如:<configuration> xxx </configuration>
// parseConfiguration才是重點
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// 這是重點
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// 環境配置
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 映射器配置
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
詳細XML的配置請參考官網:mybatis – MyBatis 3 | 配置
這裡,咱們只講環境配置,其他的篇幅有限,請自行查看源碼。
SqlSession
接下來看看SqlSession
的創建
// DefaultSqlSessionFactory#openSession() -->
// DefaultSqlSessionFactory#openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false)
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 預設環境
final Environment environment = configuration.getEnvironment();
// 事務工廠
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 上面那兩個對象,在創建SqlSessionFactory時,就已經創建好了
// 通過事務工廠創建事務
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 創建mybatis四大對象之一的Executor
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
看看四大對象之一Executor
的創建
// Configuration#newExecutor(Transaction transaction, ExecutorType executorType)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 判斷需要創建的執行器的類型
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
// 批處理執行器
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
// 重用執行器
executor = new ReuseExecutor(this, transaction);
} else {
// 簡單處理器(預設)
executor = new SimpleExecutor(this, transaction);
}
// 是否啟用二級緩存(二級緩存預設啟用)
if (cacheEnabled) {
// 此處使用的是裝飾器模式,對executor進行二次包裝
executor = new CachingExecutor(executor);
}
// 這塊是mybatis的插件處理,用代理的方式,以後再開文章講
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
Mapper的執行
Mapper的創建一節,講到mapper執行會被代理。
下麵就以StudentMapper為例,講講mapper的執行。
public interface StudentMapper {
List<Student> selectByName(@Param("name") String name);
}
當執行selectByName時候,進入到MapperMethod#execute(SqlSession sqlSession, Object[] args)方法。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
......
// 忽略insert、update、delete的邏輯,直接看select
case SELECT:
// 如果返回null或者設置了自定義的結果處理器
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
// 如果返回集合或者數組,我們的查詢會進到這裡,因為selectByName返回值是List
// 這是入口
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
// 如果返回map
} 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);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
繼續看executeForMany
方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 參數轉換,如果參數有註解,則會轉成map,且可使用param1, param2
// 例如:@Param("name")會轉成 {"name":xxx, "param1": xxx}
Object param = method.convertArgsToSqlCommandParam(args);
// 是否分頁
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
// 這是入口
result = sqlSession.selectList(command.getName(), param);
}
// 如果result不能強轉成方法的返回值(在此例子中getReturnType就是List<Studet>)
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
繼續看,因為案例中沒用到分頁,所以執行的是sqlSession.selectList(command.getName(), param);
// DefaultSqlSession#selectList(String statement, Object parameter) -->
// DefaultSqlSession#selectList(String statement, Object parameter, RowBounds rowBounds) -->
// DefaultSqlSession#selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) -->
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// MapperStatement在前面解析xml時,就已經創建了
// 忘了就看看創建SqlSessionFactory時是如何解析xml文件的mappers節點的
MappedStatement ms = configuration.getMappedStatement(statement);
// 執行器執行查詢方法
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
繼續看,executor.query方法,Mybatis-PageHelper插件就是通過攔截query方法,插入分頁參數的。
// CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 對sql進行預處理
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 創建一級緩存的key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 這是入口
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
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) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// delegate是SimpleExecutor
// 這是入口
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// BaseExecutor
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.");
}
// 是否要清除緩存,預設設置是如果非select方法,都會清除緩存。
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;
}
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;
}
下麵,重點來了,準備了這麼久,終於要查詢資料庫了
// SimpleExecutor
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();
// 重點又來了,mybatis四大對象的3個,在這裡創建
// 按順序是:ParameterHandler、ParameterHandler、StatementHandler
// 又一個裝飾器模式,實際創建的是PreparedStatementHandler(預設),但是使用RoutingStatementHandler又包了一層
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 創建jdbc的statement對象,直到這裡,才會真正獲取資料庫連接
stmt = prepareStatement(handler, ms.getStatementLog());
// 執行查詢,並使用resultHandler處理結果
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
答案
- 創建順序為:Executor、ParameterHandler、ParameterHandler、StatementHandler
- 插件的執行順序,如果都命中同一個方法,那麼順序為,越晚註冊的插件,越先執行(因為代理)