Springboot中mybatis執行邏輯源碼分析
- 2021 年 8 月 1 日
- 筆記
Springboot中mybatis執行邏輯源碼分析
在上一篇springboot整合mybatis源碼分析已經講了我們的Mapper接口,userMapper是通過MapperProxy實現的一個動態代理,所有調用userMapper的方法,最終都會代理到MapperProxy的invoke方法上,我們這次就來看看mybatis具體的執行流程。為了簡單易懂,本次的示例用的是最簡單的查詢語句且不包含事務。
本篇文檔的源碼路徑//github.com/wbo112/blogdemo/tree/main/springbootdemo/springboot-mybatis
-
我們在業務代碼中調用
userMapper.findAll()
會調用到MapperProxy的invoke方法,我就從這裡開始吧//MapperProxy類的方法 //proxy就是我們userMapper生成的代理類,method當前是findAll(),args當前是個map "{id=1, table=user}" public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //如果是Object的方法,就直接通過反射執行方法 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { //當前的方法findAll不是Object的,所以會走到這裡 return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }
//MapperProxy類的方法 private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { //這裡會將方法進行緩存。當method不存在於methodCache中時,創建一個MapperMethodInvoker,添加到methodCache中的 //methodCache你在MapperProxyFactory.newInstance方法的時候,從MapperProxyFactory類中傳遞過來的 //而MapperProxyFactory這個類是在添加mapper類的時候,MapperRegistry.addMapper方法中構造出來的, knownMappers.put(type, new MapperProxyFactory<>(type));所以我們這裡的methodCache是每個mapper類都會有一個 return MapUtil.computeIfAbsent(methodCache, method, m -> { //如果方法的修飾符是public,沒有abstract,static修飾的話就會走這裡,由於我們的mapper是接口,我們的方法也是abstract的,所以不會走到這個分支 if (m.isDefault()) { try { if (privateLookupInMethod == null) { return new DefaultMethodInvoker(getMethodHandleJava8(method)); } else { return new DefaultMethodInvoker(getMethodHandleJava9(method)); } } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } } else { //所以我們這裡最終會創建一個PlainMethodInvoker,添加到methodCache中,我們先看看MapperMethod的構造方法 return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }); } catch (RuntimeException re) { Throwable cause = re.getCause(); throw cause == null ? re : cause; } }
//MapperMethod類的方法 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { //command主要是用mapperInterface和method在config中查找對應sql的定義,返回sql id和 sqlCommandType this.command = new SqlCommand(config, mapperInterface, method); //method主要是通過解析我們的method,得到方法的返回類型,參數類型,分頁等等相關信息 this.method = new MethodSignature(config, mapperInterface, method); }
構造完 new PlainMethodInvoker之後就會調到它的invoke方法去處理,繼續調用到上面MapperMethod的execute方法,我們進去看看
//所有的增刪改查都是在這裡走不同的分支來處理的,我們當前的是查詢,我們看看select分支 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; //我們這裡的findAll()方法返回值是個List,所以會走到method.returnsMany()這個分支,我們進去看看 } 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); 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; }
//MapperMethod的方法 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; //這裡會對參數做個處理,如果mapper方法有多個參數的話,會封裝到一個map裏面,我們這裡只有一個參數,已經是map了,所以我們這裡只是從Object[]返回args[0] Object param = method.convertArgsToSqlCommandParam(args); //通過mapper方法上有沒有 RowBounds.class參數來判斷是否有分頁,我們這裡沒有 if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.selectList(command.getName(), param, rowBounds); } else { //所以我們這裡會走到這個分支中 result = sqlSession.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; }
會調用到SqlSessionTemplate的selectList方法,繼續調用到sqlSessionProxy的selectList方法,這個sqlSessionProxy也是個動態代理,是在SqlSessionTemplate構造方法中初始化的
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }
調用sqlSessionProxy的方法,最終都會調用到SqlSessionInterceptor(這是個內部類,在SqlSessionTemplate類中)的invoke方法
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //這裡會返回一個真正執行sql的SqlSession,我們進 getSqlSession方法去看看 SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); ...... }
//SqlSessionUtils的方法 public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); //如果開啟了事務,在同一個事務中,第一次返回的holder是空的,後面返回的holder都不為空,直接holder中返回SqlSession SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); //如果holder不為空,根據executorType相同,返回之前緩存的SqlSession SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } LOGGER.debug(() -> "Creating a new SqlSession"); //開啟了事務,在同一個事務中,第一次執行,或者沒有開啟事務,就會走到這裡 session = sessionFactory.openSession(executorType); //如果開啟了事務,就會將創建出來的session進行緩存 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
我們看看session = sessionFactory.openSession(executorType)這句,最終會調用到DefaultSqlSessionFactory.openSessionFromDataSource中
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); //這裡會創建一個SpringManagedTransaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //在這裡根據execType創建Executor,當前的execType是一個SIMPLE,會創建SimpleExecutor, 如果開啟了緩存,就會再創建CachingExecutor,包裝SimpleExecutor final Executor executor = configuration.newExecutor(tx, execType); //最終返回DefaultSqlSession對象 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(); } }
我們再回頭看SqlSessionInterceptor.invoke後面的執行,創建完了SqlSession,就會去調用Object result = method.invoke(sqlSession, args),我們去看看這裡的執行,這句是通過反射來執行的,這裡的sqlSession是上面創建的DefaultSqlSession類來完成的
我們來看看DefaultSqlSession的方法,所有的增刪改查方法都有具體的實現,我們最終mapper的方法都是通過這個類來實現的。
我們看看本次調用的selectList方法
//最終會調用到這個 private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { //根據sql id獲取到 MappedStatement(這個包含了我們sql語句的定義的詳細信息) MappedStatement ms = configuration.getMappedStatement(statement); //這裡會繼續去調用CachingExecutor的query方法 return executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
//CachingExecutor的方法 @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //這裡主要是會對我們的sql語句中"${"、"}"和"#{"、"}" 及之間的內容進行替換。 //將"${"、"}"根據裏面的內容直接進行字符串替換 //將"#{"、"}"替換成?,根據參數順序將參數封裝到parameterMappings中 //我們的sql語句是" SELECT * FROM ${table} where id =#{id}",我們的入參map中有table=user,這裡就會進行替換,替換之後的sql就是 "SELECT * FROM user where id =?" BoundSql boundSql = ms.getBoundSql(parameterObject); //這裡會生成一個查詢緩存的key CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); //繼續走到這裡去看看 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
//CachingExecutor的方法 @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //會在這裡獲取緩存,這裡的緩存也就是我們常說的二級緩存了,由於我們的mapper.xml文件中配置了<cache/>,所以這裡的cache也就不會為空,這個cache在<mapper>標籤下面,所以每個mapper的cache都是各自來控制的。 //這裡的緩存最終是LruCache,看名字就知道這是一種LRU(Least recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據, //Cache有多種實現,具體要那種,我們是可以指定的 //在其他的update,delete等等方法會調用 flushCacheIfRequired(ms);將緩存清空 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) { //繼續會調用到這裡,這裡的delegate是SimpleExecutor ,這個方法在它的父類,BaseExecutor中 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //在這裡會將查村出來的結果緩存到TransactionalCache.entriesToAddOnCommit中, //注意:這裡並沒有緩存到cache裏面 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
//BaseExecutor的方法 @SuppressWarnings("unchecked") @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ...... try { queryStack++; //在這裡會從 localCache中查找,這個其實就我們說的一級緩存真正存放的位置。這個localCache是當前類的一個屬性,而在沒有開啟事務的時候,我們每次都會新創建一個SimpleExecutor,所以這個localCache也就都是空的 //如果開啟事務,在同一個事務中,第一次請求會創建 SimpleExecutor,之後都是重用同一個SimpleExecutor 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(); //這裡的LocalCacheScope默認是LocalCacheScope.SESSION,如果是LocalCacheScope.STATEMENT的話就會清空緩存 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }
//BaseExecutor的方法 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 { //在這裡會獲取connection,執行數據庫的查詢,並返回我們需要的類型 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; }
我們現在退回到SqlSessionTemplate.invoke方法看獲取到結果後的處理
//SqlSessionTemplate的方法 private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { //獲取到執行結果,在這裡返回 Object result = method.invoke(sqlSession, args); //由於我們本次不涉及事務,所以會從這個分支返回 if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() //在這裡會將我們之前緩存到TransactionalCache.entriesToAddOnCommit中的返回結果,存儲到MappedStatement.cache中 //由於在添加到cache中會調用,serialize((Serializable) object),通過序列化返回byte[]數組,所以如果我們xml文件中開啟了緩存,那我們返回結果包含的類就需要實現Serializable接口 sqlSession.commit(true); } return result; } catch (Throwable t) { ...... } finally { if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
上面就是整個springboot中mybatis調用查詢的整個流程了 。