mybatis(1)- Springboot中的規範使用

@Mapper與@MapperScan正確使用

結論

  1. [auto-configuration package  路徑] 默認是啓動類包路徑。
  2. 下文測試1中 @Mapper能正常啓動是由於MybatisAutoConfiguration下的AutoConfiguredMapperScannerRegistrar來掃碼啓動類路徑包下帶@Mapper的對象。
  3. 儘可能不要自動建立SqlSessionTemplate,除非有特殊須要將SqlSessionTemplate歸於容器管理
    MapperFactoryBean先執行SqlSessionDaoSupport中的setSqlSessionFactory方法, 會新建一個SqlSessionTemplate; 但被隨後執行的setSqlSessionTemplate方法注入的託管於容器的SqlSessionTemplate實例覆蓋。

正確的使用方式有2種

  1. @Mapper + @ImportAutoConfiguration(MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class) + @SpringBootApplication(exclude = { MybatisAutoConfiguration.class })
  2. @MapperScan({ "com.noob.giant.dao" }) + @SpringBootApplication(exclude = { MybatisAutoConfiguration.class })
    (package的路徑要儘可能精確到DAO層。固然也能夠在@MapperScan 配置 annotationClass 來指定掃描的註解類型 或者 markerInterface 指定接口)

分析

MybatisAutoConfiguration 在裝載時會生成SqlSessionTemplate對象託管於容器中,致使MapperFactoryBean中setSqlSessionFactory、setSqlSessionTemplate 前後執行。java

引用javadoc對於 MybatisAutoConfiguration 的描述:
If {@link org.mybatis.spring.annotation.MapperScan} is used, or a  configuration file is specified as a property, those will be considered, otherwise this auto-configuration will attempt to register mappers based on  the interface definitions in or under the root auto-configuration package. redis

若是@MapperScan 或者特定的資源文件被加載,那就要慎重考慮了。此類會嘗試註冊auto-configuration package路徑下定義的接口爲mappersspring

而對於MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar在執行過程當中會掃描auto-configuration package路徑下帶@Mapper註解的類sql

 再來比對下MapperScannerConfigurer源碼、@MapperScan import的MapperScannerRegistrar的源碼,發現與AutoConfiguredMapperScannerRegistrar都是建立了ClassPathMapperScanner並肯定掃描路徑!
 詳見:​​​​​mybatis的MapperScan到底作了什麼mybatis

測試1

不使用@MapperScan和@Mapper:app

public interface DecisionAdjustApplyMapper {
    DecisionAdjustApply selectUnqiue(@Param("applyNo") String applyNo);
}


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.ImportResource;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableDiscoveryClient
@EnableFeignClients
@EnableConfigurationProperties // 配合@ConfigurationProperties 合用
@EnableTransactionManagement(proxyTargetClass = true) // 啓註解事務管理,等同於xml配置方式的 <tx:annotation-driven />
@EnableAsync //開啓異步調用
@EnableAspectJAutoProxy //開啓自定義切面
@ImportResource("classpath:dependence.xml")
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class})
//@MapperScan("com.noob.giant.dao")
public class GiantApplication {

    public static void main(String[] args) {
          //System.setProperty("DEPLOY_ENV", "test");
        SpringApplication.run(GiantApplication.class, args);
    }
}

結果:   拋出異常 , Mapper對象沒法注入容器! 異步

Description:
A component required a bean of type 'com.noob.giant.dao.DecisionAdjustApplyMapper' that could not be found.

Action:
Consider defining a bean of type 'com.noob.giant.dao.DecisionAdjustApplyMapper' in your configuration.

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.noob.giant.dao.DecisionAdjustApplyMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax.annotation.Resource(shareable=true, lookup=, name=, description=, authenticationType=CONTAINER, type=class java.lang.Object, mappedName=)}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1493)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:518)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:496)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:627)
	at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:169)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:318)
	... 54 common frames omitted

後續測試發現: 單獨  在Mapper類上增長@Mapper  或   在啓動類上增長「@MapperScan("com.noob.giant.dao")」 都能啓動注入成功。ide

SqlSessionTemplate 被處理了2次?

在對每個Mapper對象進行beanWrapper過程當中,建立MapperFactoryBean(extends SqlSessionDaoSupport)對象時,會先執行SqlSessionDaoSupport中的setSqlSessionFactory方法根據傳入的SqlSessionFactory (測試例子中實際對象是DefaultSqlSessionFactory建立了一個SqlSessionTemplate,然後再執行setSqlSessionTemplate方法又注入了一個SqlSessionTemplate來覆蓋原有的!spring-boot

debug發現:
AbstractAutowireCapableBeanFactory.populateBean方法中post


入參對象RootBeanDefinition的PropertyValues初始值正常:

 處理後最終獲得的PropertyValues對象中卻SqlSessionFactory SqlSessionTemplate !

在測試中發現,MapperFactoryBean兩方法中的SqlSessionTemplate 實例 內容是大體同樣(構造方式默認相同:new SqlSessionTemplate(sqlSessionFactory))。 各個不一樣Mapper經過setSqlSessionTemplate方法注入的SqlSessionTemplate 實例是同一個對象,且setSqlSessionFactory的入參SqlSessionFactory實例也是同一個對象。

因此推測: sqlSessionTemplate在另外的地方被初始化託管在Spring容器中,被當作自動注入的屬性了。

測試2 - AutoConfiguredMapperScannerRegistrar

經查驗發現:
在運行環境中引用的mybatis-spring-boot-autoconfigure.jar中有一個MybatisAutoConfiguration自動裝載了sqlSessionTemplate託管於容器,且提供了掃描@Mapper的處理類AutoConfiguredMapperScannerRegistrar。

    <parent>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot</artifactId>
        <version>1.3.2</version>
    </parent>
    <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    <name>mybatis-spring-boot-autoconfigure</name>

與其餘無此jar引用仍舊正常啓動且只會執行setSqlSessionFactory方法的項目比對後, 發現沒有MybatisAutoConfiguration加載也能啓動,但多了個配置:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
        <property name="basePackage" value="com.noob.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

第一次

@Mapper + 啓動類排除自動裝載: @SpringBootApplication(exclude = { MybatisAutoConfiguration.class })

結果:  失敗!沒法正常注入Mapper對象,報錯同上。

第二次  

@Mapper + 啓動類排除自動裝載: @SpringBootApplication(exclude = { MybatisAutoConfiguration.class }) + 啓動類增長自動裝載@ImportAutoConfiguration(MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class)

@ImportAutoConfiguration(MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class)
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, MybatisAutoConfiguration.class,
        RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class })
//@MapperScan({ "com.noob.giant"})
public class GiantApplication { ...

@ImportAutoConfiguration 改成 @Import 就沒用了!失敗在:AutoConfiguredMapperScannerRegistrar的registerBeanDefinitions時 AutoConfigurationPackages.get()獲取不到auto-configuration package 的路徑。

結果: 正常啓動, 且只執行了setSqlSessionFactory方法!!

debug發現: 

  1. AbstractAutowireCapableBeanFactory.populateBean()中處理獲得的最後PropertyValues中只有SqlSessionFactory

    後續測試時增入本身顯示聲明@Bean SqlSessionTemplate,又出現了!

    關鍵在於AbstractAutowireCapableBeanFactory的populateBean方法中執行autowireByType時對於resolveDependency結果是否爲空的斷定!

    詳細分析見:https://my.oschina.net/u/3434392/blog/3010046 的【反向驗證】

  2. MybatisAutoConfiguration 提供的 MapperScannerRegistrarNotFoundConfiguration 類自動加載的 AutoConfiguredMapperScannerRegistrar 獲取到掃描mapper類的包路徑。
    (該地址與啓動application類包路徑一致!)

第三次

啓動類增長@MapperScan({ "com.noob.giant.dao" }) + 啓動排除自動裝載: @SpringBootApplication(exclude = { MybatisAutoConfiguration.class })

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,MybatisAutoConfiguration.class, RedisAutoConfiguration.class,RedisRepositoriesAutoConfiguration.class })
@MapperScan({ "com.noob.giant.dao", "com.noob.xmagic.dao" })
public class GiantApplication { .......

結果: 具體呈現和第二次一致! 正常啓動!!且建立MapperFactoryBean只執行了setSqlSessionFactory方法!!

測試3 - auto-configuration package 必定是啓動類包路徑

咱們將啓動的Application類移動到「com.noob.giant.hh」路徑下 :
由於啓動時默認掃描的是啓動類包路徑下的對象,因此本測試用例須要在啓動Application類從新指定 @SpringBootApplication(scanBasePackages={"com.noob.giant"}, 不然加載不到bean

測試4 -  單獨使用@MapperScan時,掃描包路徑必定要精確到Dao層!

@MapperScan({ "com.noob.giant" }):

一個大範圍包路徑的話,在 ClassPathMapperScanner.doScan 時會把一些額外的接口也看成mapper。  若是精確到Dao層就沒有這個現象!

這是由於:

ClassPathBeanDefinitionScanner.doScan方法,在經過basePackage執行findCandidateComponens方法時使用了ClassPathMapperScannerisCandidateComponent方法對返回結果集斷定: 但只是斷定bean的定義描述是不是接口。因此致使doScan最終返回的BeanDefinitionHolder集合中含有不少不相干的接口信息。

@Override
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }
相關文章
相關標籤/搜索