執行具體過程(集成到spring)
- 找到掃包類ClassPathMapperScanner,和以往的掃包形式一樣,掃描包下所有類, 並獲得BeanDefinition
- 基於BeanDefinition,通過設置definition.setBeanClass,然后在spring 容器中通過getBean的方式獲取Mapper對象(此時是基礎對象下面要繼續織入插件)
- Mapper對象只有簡單持有sqlSession來做數據庫操作的能力, 而Mybatis提供了插件的功能, 就需要已實例化的Mapper對象進行再次代理, 將插件能力用方法攔截的方式編織到進去(插件要編織代碼具體執行位置依據實際情況)
Mybatis知識: 重重代理之后最終操作數據庫執行鏈 Executor -> StatementHandler -> statement.excute()-> ResultHander.handleResultSets(statement)
//spring-mybatis掃包注解 實例化ClassPathMapperScanner 並設置所需屬性值, 屬spring 范疇,忽略掉,直接關注ClassPathMapperScanner @MapperScan("com.moredian.audit.dao.mapper") public class MybatisPlusConfig { 忽略....... }
查看最關心的doScan與processBeanDefinitions方法
ClassPathMapperScanner
@Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { //使用spring自帶掃包方式 先得到所有定義類 Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { // 具體Bean處理方法 接着往下看 processBeanDefinitions(beanDefinitions); } return beanDefinitions; } //實際是對定義類的一系列設置 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { 忽略若干行...... //definition.setBeanClass 很眼熟的代碼, 設置FactoryBean 后續spring容器通過FactoryBean的getObject()方法得到具體代理對象 //getObject的實現在 MapperFactoryBean中實現, 接下來所有的東西都是圍繞MapperFactoryBean的getObject來實現的 definition.setBeanClass(this.mapperFactoryBean.getClass()); 忽略若干行....... }
實例化Mapper對象
MapperFactoryBean
@Override public T getObject() throws Exception { //這里使用spring-mybatis集成 使用的是getSqlSession()=SqlSessionTemplate return getSqlSession().getMapper(this.mapperInterface); }
委托SqlSessionTemplate來實例化Mapper
SqlSessionTemplate
@Override public <T> T getMapper(Class<T> type) { //使用mybatis configuration來實例化Mapper return getConfiguration().getMapper(type, this); } @Override public Configuration getConfiguration() { //獲取sqlSessionFactory中的configuration //在上一節MybatisPlusAutoConfiguration初始化過程中 sqlSessionFactory的configuration設置為苞米豆重寫類 return this.sqlSessionFactory.getConfiguration(); }
繼續調用方法getMapper
MybatisConfiguration
@Override public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mybatisMapperRegistry.getMapper(type, sqlSession); }
MybatisMapperRegistry
@Override 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 MybatisPlusMapperRegistry."); } try { //在這里會通過代理實例化一個Mapper對象 最終使用MapperProxy實例化代理對象 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
直接看invoke方法,Mapper每個方法都會被此方法代理執行, jdk代理方式 不過多解釋
MapperProxy
@Override 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); } //此處為執行方法類 這個方法很重要, 后面查詢時, 對於是否查詢條目邊界, 就在cachedMapperMethod方法中設置, 判斷Mapper方法參數是否含有有RowBounds子類(舉例:Page extends RowBounds) final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
到這里 Mapper代理實例化方式和方法執行過程基本結束, 接下來看Mapper方法的具體執行, 接上面最后一行 mapperMethod.execute(sqlSession, args) 以查詢列表為例查看源碼
MapperMethod
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); //通過看前面代碼可只此處sqlSession的實現為Spring的 SqlSessionTemplate, 但其實SqlSessionTemplate並沒有實現Sqlsession的功能,而是委托給Mybatis自帶的DefaultSqlSession來完成操作 result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { //通過看前面代碼可只此處sqlSession的實現為Spring的 SqlSessionTemplate, 但其實SqlSessionTemplate並沒有實現Sqlsession的功能,而是委托給Mybatis自帶的DefaultSqlSession來完成操作 result = sqlSession.<E>selectList(command.getName(), param); } 忽略....... return result; }
實際執行者為DefaultSqlSession, 接着往下看selectList
DefaultSqlSession
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { //在上一節解析XML的時候 已經將所有的statement設置到configuration 此處直接取用 MappedStatement ms = configuration.getMappedStatement(statement); //executor 有兩個實現類BaseExecutor 和 cachedExecutor(二級緩存), 為了方便代碼追蹤, 不開二級緩存,使用BaseExecutor來執行 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(); } }
我們更關注的是從數據庫取數據的執行過程, 直接跳到queryFromDatabase方法
BaseExecutor
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 忽略緩存代碼.... try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } 忽略緩存代碼.... return list; } //下面這段代碼為此篇文章最為重點的代碼 @Override public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { //啥也沒干 不需要關心 flushStatements(); //獲取 苞米豆Configuration Configuration configuration = ms.getConfiguration(); //這里是mybatis插件核心所在 非常重要 直接進入方法 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql); Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
實例化具體Statement執行器
Configuration
//實例化Statement執行器 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { //創建RoutingStatementHandler對象 用於執行Statement, 其實從名字可以看出RoutingStatementHandler 並不做真正的處理, 而是將處理過程交給其他基礎StatementHandler實現類 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); //這里就是Mybatis的精髓所在了 將創建的RoutingStatementHandler對象再次代理, 添加插件執行功能, 進入interceptorChain.pluginAll 看具體如何代理的 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
通過jdk代理方式 植入插件調用鏈
InterceptorChain
//將所有的插件interceptors 通過代理方式植入到目標對象中 public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { // 從下面這段代碼可以簡單看出,要么給target通過反射方式設置屬性,要么通過jdk重新代理當前target, 這里根據自己需要實現攔截器(也即插件) // 下面從苞米豆分頁插件(PaginationInterceptor)為例子來看怎么實現 target = interceptor.plugin(target); } return target; }
分頁插件織入
PaginationInterceptor
@Override public Object plugin(Object target) { if (target instanceof StatementHandler) { //具體包裝交給Plugin工具類來做的 直接看工具類wrap方法 return Plugin.wrap(target, this); } return target; }
mybatis插件執行包裝
Plugin
public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { //這段代碼表明 代理方法實現在Plugin 直接進Plugin查看invoke方法 return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //獲取當前類 所有需要攔截的方法 Set<Method> methods = signatureMap.get(method.getDeclaringClass()); //若方法需要攔截 則前置執行攔截器方法 if (methods != null && methods.contains(method)) { //具體的方法執行Invocation在intercept中執行, 當然這取決於是否真的需要執行 return interceptor.intercept(new Invocation(target, method, args)); } //若方法不需要攔截 則直接執行方法 return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }