MyBatis 源碼分析-技術分享

MyBatis 源碼分析java

這張圖總結的很到位::spring

  • MyBatis的主要成員
    • Configuration        
      • MyBatis全部的配置信息都保存在Configuration對象之中,
      • 配置文件中的大部分配置都會存儲到該類中
    • SqlSession            
      • 做爲MyBatis工做的主要頂層API
      • 表示和數據庫交互時的會話,完成必要數據庫增刪改查功能
    • Executor              
      • MyBatis執行器,
      • 是MyBatis 調度的核心,
      • 負責SQL語句的生成和查詢緩存的維護
    • StatementHandler
      • 封裝了JDBC Statement操做,
      • 負責對JDBC statement 的操做,
        • 如設置參數等
    • ParameterHandler  
      • 負責對用戶傳遞的參數轉換成JDBC Statement 所對應的數據類型
    • ResultSetHandler  
      • 負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合
    • TypeHandler          
      • 負責java數據類型和jdbc數據類型(也能夠說是數據表列類型)之間的映射轉換
    • MappedStatement  
      • MappedStatement維護一條<select|update|delete|insert>節點的封裝
    • SqlSource              
      • 負責根據用戶傳遞的parameterObject,
      • 動態地生成SQL語句,將信息封裝到BoundSql對象中,並返回
    • BoundSql              
      • 表示動態生成的SQL語句以及相應的參數信息
    • knownMappers.put(type, new MapperProxyFactory<T>(type));
    • sqlSessionFactory.openSession();
      • openSessionFromDataSource
        •  
  • spring-mybatis
    • ClassPathMapperScanner doScan方法的真正調用地方
      • definition.setBeanClass(this.mapperFactoryBean.getClass()); 註冊 beanDefinition class 爲mapperFactoryBean
    • xmlConfigBuilder
      • configuration = xmlConfigBuilder.getConfiguration();
    • XMLStatementBuilder
      • builderAssistant.addMappedStatement...
        • MappedStatement.Builder statementBuilder = new MappedStatement.Builder(...
    • XMLMapperBuilder  解析xml 配置文件,包括 mapper.xml 的配置
      • xmlMapperBuilder.parse();
        • bindMapperForNamespace 綁定 mapper
          • configuration.addMapper(boundType);
            • mapperRegistry.addMapper(type);
              • knownMappers.put(type, new MapperProxyFactory<T>(type));
    • return this.sqlSessionFactoryBuilder.build(configuration);
      • return new DefaultSqlSessionFactory(config);
    • MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
      • public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
          if (!this.externalSqlSession) {
            this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
          }
        }
    • SqlSessionTemplate 本質是 SqlSession 的裝飾器
      • this.sqlSessionProxy = (SqlSession) newProxyInstance(   // sqlSessionProxy  本質是 SqlSession的動態代理
            new Class[] { SqlSession.class },
            new SqlSessionInterceptor());
    • if (!isEmpty(this.plugins)) {
        for (Interceptor plugin : this.plugins) {
          configuration.addInterceptor(plugin);   // 初始化插件
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Registered plugin: '" + plugin + "'");
          }
        }
      }
      • interceptorChain.addInterceptor(interceptor);
      • protected final InterceptorChain interceptorChain = new InterceptorChain();  // InterceptorChain 是configuration 的成員變量
      • 插件的調用是在 建立四大 Handler 的時候 執行 pluginAll
        • 是在程序執行階段
          好比:doUpdate 時候,執行 newParameterHandler
          • StatementHandler handler = configuration.newStatementHandler(
            • public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
                ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
                parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
                return parameterHandler;
              }
    • processPropertyPlaceHolders

      • 執行屬性的處理,簡單的說,
        • 就是把xml中${XXX}中的XXX替換成屬性文件中的相應的值
    • findCandidateComponents
      • if (isCandidateComponent(sbd)) {
        • protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
          }
  • 初始化階段:
  • 程序運行階段:
    • 基本運行:
      • public static void main(String[] args) {
                //定義 SqlSessionFactory
                SqlSessionFactory sqlSessionFactory = null;
                try {
                    //使用配置文件建立 SqlSessionFactory
                    sqlSessionFactory = new SqlSessionFactoryBuilder().build(
                            Resources.getResourceAsReader("mybatis-config.xml"));
                } catch (IOException ex) {
                    //打印異常.
                    Logger.getLogger(MainCh1.class.getName()).fatal("建立 SqlSessionFactory失敗", ex);
                    return;
                }
                //定義 sqlSession
                SqlSession sqlSession = null;
                try {
                    //用sqlSessionFactory建立sqlSession
                    sqlSession = sqlSessionFactory.openSession();
                    //獲取Mapper
                    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
                    //執行Mapper接口方法.
                    UserPO user = userMapper.findUser(1);
                    //打印信息
                    System.err.println(user.getUsername());
                } finally {
                    //使用完後要記得關閉sqlSession資源
                    if (sqlSession != null) {
                        sqlSession.close();
                    }
                }
            }
    • spring 託管:
      • 添加配置
        <import resource="spring-mybatis.xml"/>
  • 插件:
    • 多個interceptor呢?固然是代理類又被代理了​​​​​
      • 配置的時候寫在最前面的最後執行
    • 以分頁插件爲例:
      • 須要 繼承 Interceptor
        • @Intercepts(
              {
                  @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                  @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
              }
          )
          public class PageInterceptor implements Interceptor {
      • interceptorChain.pluginAll(executor) 在configuration 內部註冊全部的 plugin
      • 本質就是getSignatureMap 方法,掃描全部註解,對配置的Signature 方法進行 動態代理
      • 代理類就是public class Plugin implements InvocationHandler
      • 執行Plugin 的invoke 會判斷該方法是否被代理(signatureMap 裏面有沒有)
      • 若是有執行 intercept 方法
      • 該方法最後一行執行的proceed 方法,其實就是該方法的invoke 執行
  • 手寫TypeHandler:
    • 詳見 催收系統CalendarTypeHandler
  • 緩存:
    • 一級緩存:
      • 一級緩存默認開啓,SqlSession 級別的
      • 驗證:
        • 相同的查詢,連續查詢兩遍,記錄查詢用時,會發現第二次快得多
        • update、 insert、delete 等語句會觸發清除緩存
      • 一級緩存,在存在倆sqlsession 時,可能存在髒數據的狀況
        • 好比,sqlsessionA 兩次相同查詢t 表中間,
        • sqlsessionB 更新了t表數據,
        • sqlsessionA 第二次查詢的數據就是可能已被修改的髒數據
    • 二級緩存:
      • 二級緩存默認關閉,SqlSessionFactory 級別的 
      • 更不靠譜,開啓方式:
  • N+1問題:
    • 存在級聯查詢(嵌套查詢)中
      • 外層查詢的一條結果數據,由內層查詢得到
      • 外層查詢一次,得到結果數N ,就要進行N 次內層查詢
        • (官方不鼓勵使用,這樣產生大量1+N次查詢)
    • 因爲1+N 問題的性能損耗,能夠考慮配合使用 延時加載
    • 官網解釋:
  • lazy loading 是怎麼作到的?
    • 懶加載在級聯查詢時用到了,SimpleStatementHandler 裏面query結果
    • DefaultResultSetHandler 處理結果
    • handleResultSets -->handleResultSets -->...getRowValue-->createResultObject 
    • 若是有嵌套查詢且開啓了懶加載 那麼會使用代理工廠來處理(代理工廠類型cglib或javasissit類型(默認))
    • 針對某一個屬性,當執行
      • 新版本,已經變化:
      • 解釋爲何是「或」的關係:lazyLoadTriggerMethods 包含該方法的時候,
        • 說明對象再進行 equals、clone比較,須要全部屬性所有查詢來才能進行
          • protected Set<String> lazyLoadTriggerMethods =
            • new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
    • 在嵌套查詢的時候 get 方法會觸發 ResultLoaderMap LoadPair load() 方法去查詢(我看源代碼的理解)
      • set 方法不會觸發
    • 我找到了觸發函數lazyLoadTriggerMethods 裏面沒有get/is 
      • 依賴的是PropertyNamer.isGetter(methodName)
相關文章
相關標籤/搜索