Mybatis與Spring集成時都做了什么?


  Mybatis是java開發者非常熟悉的ORM框架,Spring集成Mybatis更是我們的日常開發姿勢。

  本篇主要講Mybatis與Spring集成所做的事情,讓讀過本文的開發者對Mybatis和Spring的集成過程,有清晰的理解。

  注:若文中有錯誤或其他疑問,歡迎留下評論。

  以mybatis-spring-2.0.2 為例,工程划分六個模塊。

1、annotation 模塊

  

 

 

  定義了@MapperScan和@MapperScans,用於掃描mapper接口。以及mapper掃描注冊器(MapperScannerRegistrar),掃描注冊器實現了 ImportBeanDefinitionRegistrar接口, 在Spring容器啟動時會運行所有實現了這個接口的實現類,
注冊器內部會注冊一系列MyBatis相關Bean。

2、batch 模塊

  

 

 

 

 

 

  批處理相關,基於優秀的批處理框架Spring batch 封裝了三個批處理相關類:

  • MyBatisBatchItemWriter(批量寫)
  • MyBatisCursorItemReader(游標讀)
  • MyBatisPagingItemReader(分頁讀)

  在使用Mybatis時,方便的應用Spring  batch,詳見 Spring-batch使用

3、config模塊

  

 

  解析、處理讀取到的配置信息。

4、mapper模塊

  

 

 

  這里是處理mapper的地方:ClassPathMapperScanner(根據路徑掃描Mapper接口)與MapperScannerConfigurer 配合,完成批量掃描mapper接口並注冊為MapperFactoryBean。

5、support 模塊

  

 

  支持包,SqlSessionDaoSupport 是一個抽象的支持類,用來為你提供 SqlSession調用getSqlSession()方法會得到一個SqlSessionTemplate。

6、transaction 模塊,以及凌亂類

  

 

 

 

 

 

  與Spring集成后,事務管理交由Spring來做。

  還有包括異常轉換,以及非常重要的SqlSessionFactoryBean,在外散落着。

下面重點講述幾個核心部分: 

  一、初始化相關

  1)SqlSessionFactoryBean

  在基礎的MyBatis中,通過SqlSessionFactoryBuilder創建SqlSessionFactory。集成Spring后由SqlSessionFactoryBean來創建。   

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>...

  需要注意SqlSessionFactoryBean實現了Spring的FactoryBean接口。這意味着由Spring最終創建不是SqlSessionFactoryBean本身,而是 getObject()的結果。我們來看下getObject()

 @Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { //配置加載完畢后,創建SqlSessionFactory
 afterPropertiesSet(); } return this.sqlSessionFactory; }

  getObject()最終返回了當前類的 SqlSessionFactory,因此,Spring 會在應用啟動時創建 SqlSessionFactory,並以 sqlSessionFactory名稱放進容器。

  2)  兩個重要屬性:

    1. SqlSessionFactory 有一個唯一的必要屬性:用於 JDBC 的 DataSource不能為空,這點在afterPropertisSet()中體現。

    2. configLocation,它用來指定 MyBatis 的 XML 配置文件路徑。通常只用來配置 <settings>相關。其他均使用Spring方式配置

 5   public void afterPropertiesSet() throws Exception {  6     //dataSource不能為空
 7     notNull(dataSource, "Property 'dataSource' is required");  8     //有默認值,初始化 = new SqlSessionFactoryBuilder()
 9     notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); 10     //判斷configuration && configLocation有且僅有一個
11     state((configuration == null && configLocation == null) || 
          !(configuration != null && configLocation != null), 12 "Property 'configuration' and 'configLocation' can not specified with together"); 13 //調用build方法創建sqlSessionFactory 14 this.sqlSessionFactory = buildSqlSessionFactory(); 15 }

     buildSqlSessionFactory()方法比較長所以,這里省略了一部分代碼,只展示主要過程,看得出在這里進行了Mybatis相關配置的解析,完成了Mybatis核心配置類Configuration的創建和填充,最終返回SqlSessionFactory。

 1 protected SqlSessionFactory buildSqlSessionFactory() throws Exception {  2 
 3     final Configuration targetConfiguration;  4 
 5     XMLConfigBuilder xmlConfigBuilder = null;  6    // 如果自定義了 Configuration,就用自定義的
 7     if (this.configuration != null) {  8       targetConfiguration = this.configuration;  9       if (targetConfiguration.getVariables() == null) { 10         targetConfiguration.setVariables(this.configurationProperties); 11       } else if (this.configurationProperties != null) { 12         targetConfiguration.getVariables().putAll(this.configurationProperties); 13  } 14     // 如果配置了原生配置文件路徑,則根據路徑創建Configuration對象
15     } else if (this.configLocation != null) { 16       xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream()
        , null, this.configurationProperties); 17 targetConfiguration = xmlConfigBuilder.getConfiguration(); 18 } else {21    // 兜底,使用默認的 22 targetConfiguration = new Configuration(); 23    //如果configurationProperties存在,設置屬性 24    Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables); } 26 //解析別名,指定包    27 if (hasLength(this.typeAliasesPackage)) { 28 scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream() 29 .filter(clazz -> !clazz.isAnonymousClass())
      .filter(clazz -> !clazz.isInterface()) 30 .filter(clazz -> !clazz.isMemberClass())
      .forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
31 } 32 //解析插件 33 if (!isEmpty(this.plugins)) { 34 Stream.of(this.plugins).forEach(plugin -> { 35 targetConfiguration.addInterceptor(plugin);38 } 39 ... 40 //如果需要解決原生配置文件,此時開始解析(即配置了configLocation) 41 if (xmlConfigBuilder != null) { 42 try { 43 xmlConfigBuilder.parse(); 44    ... //有可能配置多個,所以遍歷處理(2.0.0支持可重復注解) 52 if (this.mapperLocations != null) { 53 if (this.mapperLocations.length == 0) {
      for (Resource mapperLocation : this.mapperLocations) { 57 ... //根據mapper路徑,加載所以mapper接口 62 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), 63 targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); 64 xmlMapperBuilder.parse(); 65  //構造SqlSessionFactory 70 return this.sqlSessionFactoryBuilder.build(targetConfiguration); 71 }

  二、事務管理

  1)事務管理器配置

    MyBatis-Spring 允許 MyBatis 參與到 Spring 的事務管理中。 借助 Spring 的 DataSourceTransactionManager 實現事務管理。  

/** 一、XML方式配置 **/ <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <constructor-arg ref="dataSource" />
</bean> /** 一、注解方式配置 **/ @Bean public DataSourceTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); }
注意:為事務管理器指定的 DataSource 必須和用來創建 SqlSessionFactoryBean 的是同一個數據源,否則事務管理器就無法工作了。

  配置好 Spring 的事務管理器,你就可以在 Spring 中按你平時的方式來配置事務。並且支持 @Transactional 注解(聲明式事務)和 AOP 風格的配置。在事務處理期間,一個單獨的 SqlSession 對象將會被創建和使用。當事務完成時,這個 session 會以合適的方式提交或回滾。無需DAO類中無需任何額外操作,MyBatis-Spring 將透明地管理事務。

  2) 編程式事務:

  推薦TransactionTemplate 方式,簡潔,優雅。可省略對 commit 和 rollback 方法的調用。    

1 TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); 2 transactionTemplate.execute(txStatus -> { 3  userMapper.insertUser(user); 4   return null; 5 });
注意:這段代碼使用了一個映射器,換成SqlSession同理。

  三、SqlSession

  在MyBatis 中,使用 SqlSessionFactory 來創建 SqlSession。通過它執行映射的sql語句,提交或回滾連接,當不再需要它的時候,可以關閉 session。使用 MyBatis-Spring 之后,我們不再需要直接使用 SqlSessionFactory 了,因為我們的bean 可以被注入一個線程安全的 SqlSession,它能基於 Spring 的事務配置來自動提交、回滾、關閉 session。

  SqlSessionTemplate  

  SqlSessionTemplate 是SqlSession的實現,是線程安全的,因此可以被多個DAO或映射器共享使用。也是 MyBatis-Spring 的核心。

  四、映射器

  1) 映射器的注冊  

 1 /** 
 2  *@MapperScan注解方式 
3 */ 4 @Configuration 5 @MapperScan("org.mybatis.spring.sample.mapper") 6 public class AppConfig { 8 } 10 /** 11 *@MapperScanS注解 (since 2.0.0新增,java8 支持可重復注解) 12 * 指定多個路徑可選用此種方式 13 */ 14 @Configuration 15 @MapperScans({@MapperScan("com.zto.test1"), @MapperScan("com.zto.test2.mapper")}) 16 public class AppConfig { 18 }
<!-- MapperScannerConfigurer方式,批量掃描注冊 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="basePackage" value="com.zto.test.*" />
 <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

  無論使用以上哪種方式注冊映射器,最終mapper接口都將被注冊為MapperFactoryBean。既然是FactoryBean,我們來跟它的getObject()方法看下。

  2) MapperFactoryBean源碼解析

    1.查找MapperFactoryBean.getObject()  

1 /**
2  * 通過接口類型,獲取mapper 3  * {@inheritDoc} 4    */
5  @Override 6   public T getObject() throws Exception { 7     //getMapper 是一個抽象方法
8     return getSqlSession().getMapper(this.mapperInterface); 9   }

    2.查看實現類,SqlSessionTemplate.getMapper()

    ( 為什么是SqlSessionTemplate,而不是默認的DefaultSqlSession?SqlSessionTemplate是整合包的核心,是線程安全的SqlSession實現,是我們@Autowired mapper接口編程的基礎 )

4  @Override 5 public <T> T getMapper(Class<T> type) { 6 return getConfiguration().getMapper(type, this); 7 }

    3.調用Configuration.getMapper()  

1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 2     return mapperRegistry.getMapper(type, sqlSession); 3 }

    4.調用MapperRegistry.getMapper()   

 1 @SuppressWarnings("unchecked")  2   public <T> T getMapper(Class<T> type, SqlSession sqlSession) {  3     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);  4     if (mapperProxyFactory == null) {  5       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");  6  }  7     try {  8       return mapperProxyFactory.newInstance(sqlSession);  9     } catch (Exception e) { 10       throw new BindingException("Error getting mapper instance. Cause: " + e, e); 11  } 12 }

    5.調用MapperProxyFactory.newInstance()  

1 @SuppressWarnings("unchecked") 2   protected T newInstance(MapperProxy<T> mapperProxy) { 3     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); 4  }

    最終看到動態代理生成了一個新的代理實例返回了,也就是說,我們使用@Autowired 注解進來一個mapper接口,每次使用時都會由代理生成一個新的實例。

    為什么在Mybatis中SqlSession是方法級的,Mapper是方法級的,在集成Spring后卻可以注入到類中使用?

    因為在Mybatis-Spring中所有mapper被注冊為FactoryBean,每次調用都會執行getObject(),返回新實例。

  五、總結

    MyBatis集成Spring后,Spring侵入了Mybatis的初始化和mapper綁定,具體就是:

    1)Cofiguration的實例化是讀取Spring的配置文件(注解、配置文件),而不是mybatis-config.xml

    2)mapper對象是方法級別的,Spring通過FactoryBean巧妙地解決了這個問題

    3)事務交由Spring管理

    注:如對文中內容有疑問,歡迎留下評論共同探討。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM