本文mybatis-spring-boot探討在springboot工程中mybatis相關對象的註冊與載入。 建議先瞭解mybatis在spring中的使用和springboot自動裝載機制,再看此文章。 傳送門:Mybatis源碼解讀-配置載入和Mapper的生成 問題 @MapperScan和 ...
本文mybatis-spring-boot
探討在springboot工程中mybatis相關對象的註冊與載入。
建議先瞭解mybatis在spring中的使用和springboot自動裝載機制,再看此文章。
傳送門:Mybatis源碼解讀-配置載入和Mapper的生成
問題
@MapperScan
和@Mapper
能一起用嗎?
使用
-
創建工程不再贅述,參考demo
-
編寫Mapper
Mapper的註冊有兩種方式:
- 在Mapper添加
@Mapper
註解 - 在Application類添加
@MapperScan
註解確定掃描包路徑
後面會講解這兩種方式的區別
- 在Mapper添加
SqlSessionFactory和SqlSession
在討論自動裝配方式之前,先看看mybatis最簡潔的demo
public static void main(String[] args) throws Exception {
// 配置文件路徑
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 1.讀取配置,創建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.通過工廠獲取SqlSession
SqlSession session = sqlSessionFactory.openSession();
try {
// 3.獲取mapper代理對象
StudentMapper mapper = session.getMapper(StudentMapper.class);
// 4.執行查詢,此處才真正連接資料庫
System.out.println(mapper.selectByName("張三"));
} finally {
// 5.關閉連接
session.close();
}
}
可以看到,首先需要創建SqlSessionFactory和SqlSession,在springboot中,這兩者通過自動裝配完成。
在mybatis-spring-boot-autoconfigure-x.x.x.jar的spring.factories中,可以看到自動裝配註入了MybatisAutoConfiguration
類
public class MybatisAutoConfiguration implements InitializingBean {
......
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
......
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
}
SqlSessionTemplate
是SqlSession
的子類,所以現在二者都有了。
Mapper
Mapper的掃描分兩種方式討論
-
@MapperScan方式
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { }
可以看到,導入了MapperScannerRegistrar類
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); ...... registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } }
因為MapperScannerRegistrar實現了ImportBeanDefinitionRegistrar介面,所以會被調用registerBeanDefinitions方法,最後註冊MapperScannerConfigurer
咱們先記住MapperScannerConfigurer這個類,去看看@Mapper的方式
-
@Mapper方式
在
MybatisAutoConfiguration
中,有這麼一段代碼@org.springframework.context.annotation.Configuration // 如果滿足條件,則導入AutoConfiguredMapperScannerRegistrar @Import(AutoConfiguredMapperScannerRegistrar.class) // 如果MapperFactoryBean和MapperScannerConfigurer都沒註冊,則滿足條件 @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class }) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { ...... }
我們在@MapperScan方式看到,是已經註冊了MapperScannerConfigurer類的。所以,@MapperScan會覆蓋@Mapper
繼續看看
AutoConfiguredMapperScannerRegistrar
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { ...... BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("annotationClass", Mapper.class); ...... registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } ...... }
可以看到,同樣是註冊了MapperScannerConfigurer
也就是兩種註解方式都是通過MapperScannerConfigurer掃描mapper註冊的
-
通用部分
繼續追蹤MapperScannerConfigurer的調用鏈
// MapperScannerConfigurer#postProcessBeanDefinitionRegistry public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { ...... ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); ...... // 註冊過濾器(@Mapper和@MapperScan的區別體現在這裡) scanner.registerFilters(); // 開始掃描bean scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } public void registerFilters() { boolean acceptAllInterfaces = true; // 如果指定了掃描類型(@Mapper走這裡) // annotationClass在前面的AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions被註入 // 就是這段builder.addPropertyValue("annotationClass", Mapper.class); if (this.annotationClass != null) { addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } ...... // 如果沒指定掃描類型,則掃描全部(@MapperScan走這裡) if (acceptAllInterfaces) { addIncludeFilter((metadataReader, metadataReaderFactory) -> true); } // exclude package-info.java addExcludeFilter((metadataReader, metadataReaderFactory) -> { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); }); }
看完了過濾器的註冊,繼續回到掃描邏輯scanner.scan
// ClassPathMapperScanner#scan(String... basePackages) --> // ClassPathMapperScanner#doScan(String... basePackages) public Set<BeanDefinitionHolder> doScan(String... basePackages) { // 掃描mapper(此時是原始對象) 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 { // 通過MapperFactoryBean類將mapper對象轉換成代理對象MapperProxy processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
答案
@MapperScan
和@Mapper
能一起用(不會報錯),但是@Mapper
是沒有效果的。