SSM源碼分析之Mybatis06-MyBatis總結

Mybatis源碼分析06-MyBatis總結

前言

前面我們分析了mybatis的源碼以及手寫了兩個版本的微型mybatis,相信大家對mybatis源碼一定有了更深入的瞭解了。
分享一句名言:學而不思則罔,思而不學則殆!
這節我們來思考一些問題。以及對mybatis集成spring的jar包做一個分析!

mybatis源碼回顧

分析mybatis源碼的過程中,我拿出幾個問題,不妨一起思考一下?

  1. org.apache.ibatis.binding.MapperProxy#invoke 這個類的53行什麼時候執行?

在這裏插入圖片描述

 /**
   * Backport of java.lang.reflect.Method#isDefault()
   */
  private boolean isDefaultMethod(Method method) {
    return ((method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
        && method.getDeclaringClass().isInterface();
  }

判斷一下當前類是接口,並且,類的修飾符可以是abstract或public或static~

  1. TestMapper 作者爲什麼要設計這樣的形式來做?爲什麼不是一個class而是一個interface?
public interface TestMapper {
   Test selectByPrimaryKey(Integer userId);
}

因爲這個類要來做代理,只需要獲取其中的方法名稱,方法參數即可,不需要實現類來做多餘的事,並且在spring注入的時候也是接口。

  1. org.apache.ibatis.executor.BaseExecutor#queryFromDatabase 322行這行代碼的意義

  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);//322
    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;
  }
		

查找一下這個值在哪裏put的?
ctrl+h搜索一下EXECUTION_PLACEHOLDER:
在這裏插入圖片描述
看canload()在哪裏引用的?
在這裏插入圖片描述
這裏還是調用,我們再看重寫的這個方法deferLoad
在這裏插入圖片描述
看看CachingExecutor:
在這裏插入圖片描述
既然這裏沒有put,我們看一下DefaultResultSetHandler:
在這裏插入圖片描述
找到了!
這裏是判斷了一下是否是有緩存,有緩存,則調用deferLoad方法賦值
在這裏插入圖片描述
回到322行源碼,我們再看看這裏的邏輯,key不能爲空,並且,當前的key對象的value不能是這個佔位符,纔可以load():
在這裏插入圖片描述
佔位符:
在這裏插入圖片描述
什麼情況下存在佔位符呢?就是doQuery()執行的過程中,比如執行了100s,在這個過程中,告訴別人說,當前這個key的狀態是: i’m basy! 這樣就有效的防止併發~

怎麼理解這種解決併發的策略呢?舉個栗子:

雙十一,大家在搶單同一樣商品,如果這件商品沒有做熱備(熱加載)的話,所有的併發請求都要直接走數據庫了,數據庫毫無疑問會崩潰!
在這裏插入圖片描述
所以,我們可以採取佔位符的方式:
比如用戶一最先訪問到product1:
那麼我們在Cache裏put(pid=1,placeholder),表示product1正在被操作
如果此時有用戶二訪問product1:
則首先判斷get(pid=1) ==placeholder
有佔位符則表示此商品正在被操作,其他用戶必須排隊等待了!

通過佔位符防止高併發的這種方式可以說是簡單粗暴
如果有10萬的dps,那麼通過此舉可以阻止99999的請求,只允許一個請求訪問數據庫!

題外:限流策略還有很多,比如查詢到佔位符,sleep(100)或可以使用布隆過濾

  1. lazy loading是怎麼做到的?
    不妨看源碼org.apache.ibatis.executor.resultset.DefaultResultSetHandler.createResultObject():
 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    this.useConstructorMappings = false; // reset previous mapping result
    final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();
    final List<Object> constructorArgs = new ArrayList<Object>();
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      for (ResultMapping propertyMapping : propertyMappings) {
        // issue gcode #109 && issue #149
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
          break;
        }
      }
    }
    this.useConstructorMappings = (resultObject != null && !constructorArgTypes.isEmpty()); // set current mapping result
    return resultObject;
  }

懶加載:如果當前的ResultMapping的下一個id不爲空,並且是懶加載,那麼使用代理創建的Object。
在這裏插入圖片描述
之前章節我們講到過這個配置。兩種代理模式,一種是cglib代理,另一種是jdk代理。

  1. plugin功能如何實現?

整個過程四個重要的類我們需要關注:

  • InterceptorChain
  • Plugin
  • Invocation
  • Interceptor

簡單的實現Plugin:
a) 先實現Interceptor 接口

@Intercepts(value={
        @Signature(
                type = Executor.class, // 只能是: StatementHandler | ParameterHandler | ResultSetHandler | Executor 類或者子類
                method = "query", // 表示:攔截Executor的query方法
                args = {  // query 有很多的重載方法,需要通過方法簽名來指定具體攔截的是那個方法
                        MappedStatement.class,
                        Object.class,
                        RowBounds.class,
                        ResultHandler.class
                }
                /**
                 * type:標記需要攔截的類
                 * method: 標記是攔截類的那個方法
                 * args: 標記攔截類方法的具體那個引用(尤其是重載時)
                */
        )})
public class TestPlugin implements Interceptor {
    /**
     * 具體攔截的實現邏輯
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        System.out.println("----------- intercept query start.... ---------");

        MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
        BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]);
        System.out.println(String.format("plugin output sql = %s , params = %s",boundSql.getSql(),boundSql));
        // 調用方法,實際上就是攔截的方法
        Object result = invocation.proceed();

        System.out.println("----------- intercept query end.... ---------");

        return invocation.proceed();
    }

    /**
     * 插入插件
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        // 調用Plugin工具類,創建當前的類的代理類
        return Plugin.wrap(target, this);
    }

    /**
     *  設置插件屬性
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {

    }
}

b) 然後在配置文件裏引入即可:

在這裏插入圖片描述
看官網上plugins的介紹:
在這裏插入圖片描述
只有在初始化到以上四個類的時候會plugin ,這也就確定了@Signature的type字段。

我們在debug一下之前我們訪問通過mybatis訪問數據庫的demo,發現容器啓動,就加載plugins:

在這裏插入圖片描述
然後是pluginAll(處理多個plugin):
在這裏插入圖片描述
org.apache.ibatis.plugin#Plugin:
在這裏插入圖片描述
在Incocation被invoke的同時又被invoke:
在這裏插入圖片描述
整個過程類似於aop的動態代理:

在這裏插入圖片描述

mybatis-spring.jar

spring與mybatis整合(通過@Configuration將mybatis的類註冊到spring)
在這裏插入圖片描述
思路:用mybatis programming(編程式)->managed(集成式)
怎麼集成?

  • xml
  • annotation

好的,我們依照慣例,打開源碼:
在這裏插入圖片描述
這裏annotation包就是以註解的形式整合
這裏的mapper包就是以編程式形式整合

我們先看annotation形式:
MapperScannerRegistrar實現於ImportBeanDefinitionRegistrar接口
ImportBeanDefinitionRegistrar是spring對暴露對外的框架,整合以註解形式的所有bean。

在這裏插入圖片描述
我們看代碼的59行,就是先解析到org.mybatis.spring.annotation#MapperScan
然後將MapperScan的所有註解掃描,進行匹配
在這裏插入圖片描述

下圖是spring的ImportBeanDefinitionRegistrar接口:
在這裏插入圖片描述
通過以上我們瞭解:
spring容器在處理@Configuration的時候會去調用第三方的實現類MapperScannerRegistrar調用registerBeanDefinitions去把mybatis的所有需要的類註冊進來。
那麼,我們同樣可以自定義這個實現類!

題外話:
在這裏插入圖片描述
註冊bean的時候,可以註冊接口,spring就可以自動將所有的實現類全部注入!
題外話結束~!

回到正題:
mybatis的dao層的多個mapper被掃描到,怎麼知道當前的要掃描的類就是Mapper.java?
org.mybatis.spring.mapper#isCandidateComponent
在這裏插入圖片描述
這裏就是判斷是否爲接口,是否是獨立的類

我們先看xml形式,實際上與annotation的形式類似:
在這裏插入圖片描述
在這裏插入圖片描述
那麼是sqlsession集成方式?

在這裏插入圖片描述
我們說編程式與當前spring-mybatis集成最大的區別就在於:sqlsession的實現方式不同,一個是SqlSessionTemplate,另一個是DefaultSqlSession!

在這裏插入圖片描述
我們看debug,從這裏開始不同,直接進入SqlSessionTemplate
在這裏插入圖片描述
SqlSessionTemplate這裏做了一個代理:
在這裏插入圖片描述
代理做了什麼事情呢?
我們看invoke方法:
在這個SqlSessionTemplate裏,我們看這裏是獲取了sqlsession,而不是我們在demo裏手動調用,這也就是爲什麼我們使用ssm框架,直接用mapper的方法就可以了,sqlsession其實是在雙層代理的invoke裏實現的!
在這裏插入圖片描述
再看getSqlsession:
在這裏插入圖片描述
看一下getSqlSession()方法的實現,在template下面,這個util裏有三個方法,一個是獲取sqlsession,一個是關閉sqlsession,另一個是註冊SessionHolder:
在這裏插入圖片描述

這裏注意一個問題:
之前我們總結Mapper的生命週期是method,但是現在爲什麼是單例(容器級別)呢?
在這裏插入圖片描述
mapper的作用是什麼?
找sql

總結

代碼地址:本節github代碼地址

發佈了57 篇原創文章 · 獲贊 5 · 訪問量 5023
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章