一、springboot整合mybatis
pom引入
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
mybatis的starter工程的作用只是用来导入相关依赖,该工程中是没有代码的,只有pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
其中真正处理自动装配的包是mybatis-spring-boot-autoconfigure
二、mybatis自动装配核心JAR包:mybatis-spring-boot-autoconfigure
在META-INF包下有spring.factories文件(springboot自动装配的关键,之后的文章会详细说明)
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
在这个包下只有5个类,分别是
ConfigurationCustomizer MybatisAutoConfiguration MybatisLanguageDriverAutoConfiguration MybatisProperties SpringBootVFS
这5个里我们只需要关注MybatisAutoConfiguration和MybatisProperties这两个类
三、MybatisProperties配置文件
这个类本质就是配置mybatis相关的属性,在application.yml或者application.properties文件中配置
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
private String configLocation;
private String[] mapperLocations;
private String typeAliasesPackage;
private Class<?> typeAliasesSuperType;
private String typeHandlersPackage;
private boolean checkConfigLocation = false;
private ExecutorType executorType;
}
四、自动装配的核心类-MybatisAutoConfiguration
1、装配的前置条件
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
}
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) 保证引入了mybatis和spring-mybatis相关的jar
@ConditionalOnSingleCandidate(DataSource.class) 保证容器中DataSource单例对象或者有指定主实现类
@EnableConfigurationProperties(MybatisProperties.class) 保证加载了MybatisProperties.class配置对象
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) 在容器装配DataSourceAutoConfiguration.class之后才装配当前类
2、Bean初始化方法执行
作用是判断是否使用了mybatis的xml文件,并且校验文件是否存在
@Override
public void afterPropertiesSet() {
checkConfigFileExists();
}
private void checkConfigFileExists() {
if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
Assert.state(resource.exists(),
"Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
}
}
3、构建mybatis的SqlSessionFactory
在类上的条件装配中@ConditionalOnSingleCandidate(DataSource.class)保证了DataSource肯定已经存在于容器中
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
return factory.getObject();
}
4、构建SqlSessionTemplate
@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);
}
}
5、扫描Mapper
这里有个很关键的点就是@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
为了防止使用了**@MapperScan注解后两次重复注册,因为在上一章讲的@MapperScan**注解会触发创建MapperFactoryBean和 MapperScannerConfigurer
ps:@ConditionalOnMissingBean注解的作用是当容器中不存在指定的类实例时才装载当前类
@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
@Override
public void afterPropertiesSet() {
logger.debug(
"Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
在类上的注解中,注册Mapper的关键在于 @Import(AutoConfiguredMapperScannerRegistrar.class)
该注解会触发AutoConfiguredMapperScannerRegistrar实例化
6、AutoConfiguredMapperScannerRegistrar对象实例化
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
return;
}
logger.debug("Searching for mappers annotated with @Mapper");
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
if (propertyNames.contains("lazyInitialization")) {
// Need to mybatis-spring 2.0.2+
builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
}
if (propertyNames.contains("defaultScope")) {
// Need to mybatis-spring 2.0.6+
builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
}
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
这里的注册逻辑与mybatis-spring中的MapperScan触发的MapperScannerRegistrar实例化类似,都是通过注册MapperScannerConfigurer对象来触发Mapper注册到IOC容器
有两个关键区别点
1、该扫描哪个包下的类
2、扫描什么样子类
①扫描的包packages
通过获取启动类的包路径来获取packages
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
beanFactory的来源是通过实现implements BeanFactoryAware接口完成注入的
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
②扫描什么样子的类
在这种自动装配的模式下,需要注入的Mapper需要加@Mapper注解的,如果不加是注册不到IOC容器中的
builder.addPropertyValue("annotationClass", Mapper.class);
五、总结
1、Mybatis自动装配扫描Mapper实质
无论是之前的mybatis-spring中的@MapperScan方式还是springboot自动装配的方式,实际都是先将MapperScannerConfigurer对象注入到IOC容器,从而在MapperScannerConfigurer内部触发扫描Mapper注入的逻辑
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 扫描Mapper
}
2、@MapperScan和@Mapper使用
之前有段时间也是一直不知道这两个注解是要怎么配合使用,后来通过源码知道怎么使用的,所以推荐大家还是多看源码
@MapperScan注解是配置扫描mapper包的
@Mapper 自动装配情况下,扫描mapper是从启动类包下开始的,所以使用@Mapper用来标注当前类是需要注入的Mapper