前言:本文主要基于springboot项目,从springboot的拓展角度来分析mybatis是如何与springboot集成的。
1.1、maven pom文件依赖
imalvisc-parent
com.imalvisc
1.0.0
4.0.0
spring-mybatis
org.projectlombok
lombok
mysql
mysql-connector-java
com.alibaba
druid-spring-boot-starter
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.2、application.yml配置文件
###服务器配置
server:
port: 9001
spring:
application:
name: user
###druid数据源配置
datasource:
name: dataSource
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/imalvisc?useSSL=false
username: root
password: imalvisc
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 1 from dual
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-open-prepared-statements: 50
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
###mybatis数据源配置
mybatis:
type-aliases-package: com.imalvisc.spring.mybatis.model
configuration:
map-underscore-to-camel-case: true
1.3、application启动类
package com.imalvisc.spring.mybatis;
import com.imalvisc.spring.mybatis.mapper.UserInfoMapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
@MapperScan(basePackages = {"com.imalvisc.spring.mybatis.mapper"})
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args);
UserInfoMapper userInfoMapper = applicationContext.getBean(UserInfoMapper.class);
System.out.println(userInfoMapper.selectAll());
}
}
1.4、启动测试
以上就已经将springboot集成mybatis环境搭建完成。下面进行集成原理的解读。
2.1、在项目中引入了mybatis-spring-boot-starter依赖,mybatis-spring-boot-starter又引进了mybatis-spring-boot-autoconfigure依赖,在mybatis-spring-boot-autoconfigure的类路径下,可以看到META-INF目录下有一个spring.factories文件,文件里面的内容是org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration,这里的意思是springboot项目加载时,会扫描项目本身以及jar包类路径下的META-INF目录的spring.factories文件,然后会加载执行文件里面所配置到的类,同时这就是springboot自动配置的真谛所在。
2.2、查看MybatisAutoConfiguration类发现,这是一个Configuration类,@AutoConfigureAfter(DataSourceAutoConfiguration.class)代表在DataSourceAutoConfiguration类加载完后再加载,这里是因为mybatis环境需要依赖数据源配置,所以必须在数据源配置完成后才进行mybatis配置。在MybatisAutoConfiguration类中,配置了SqlSessionFactory类,所在到这里mybatis的环境就已经生效了。
@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.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
2.3、SqlSessionFactory配置完成后,剩下的就是扫描mapper接口并生成代理类存放到IOC容器中,这样就可以依赖注入Mapper了。
2.4、在Application启动类中,加上了@MapperScan(basePackages = {"com.imalvisc.spring.mybatis.mapper"})注解,打开MapperScan注解可以发现,MapperScan加上另一个注解@Import(MapperScannerRegistrar.class),@Import注解这里简单说明下其作用是spring环境监测到@Import注解时,会加载其指定的配置类,具体详细作用可以另查阅资料。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
2.5、查看MapperScannerRegistrar发现,其实现了ImportBeanDefinitionRegistrar接口,该接口的作用是spring会调用实现该接口的registerBeanDefinitions方法,传入AnnotationMetadata和BeanDefinitionRegistry两个参数,AnnotationMetadata的作用是封装了加上@Import注解的注解的属性,这里解析的可能有点绕口,举例说明就是@MapperScan注解加上了@Import注解,@MapperScan有一个basePackages属性,所以AnnotationMetadata封装了@MapperScan注解的basePackages属性的值。
2.6、MapperScannerRegistrar的registerBeanDefinitions方法中,获取了@MapperScan注解的属性后,调用了自身的重载registerBeanDefinitions方法,重载registerBeanDefinitions方法通过调用ClassPathMapperScanner的doScan方法就完成了Mapper的扫描并加入到spring容器中,到了这里整个mybatis的环境就完全生效了。doScan方法里面的深入逻辑这里就不展开了,需要继续深入剖析的同学可以另查阅资料。
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry);
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);
Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("basePackages"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(
Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
.map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
结语:mybatis集成到springboot中,主要是基于两大核心特性,一是springboot的自动配置特性,在路径下的META-INF目录的spring.factories文件配置需要加载的类,二是利用spring的@Import的ImportBeanDefinitionRegistrar特性,根据注解上指定的属性来加载配置。其实其它的技术(如Redis、RabbitMQ、JPA、Swagger等等)通过自动配置的方法集成到springboot环境中,基本都是通过这两种特性来实现,所以希望本文章不仅仅是帮助到同学们理解springboot集成mybatis的原理,更多的希望同学们可以理解springboot环境的拓展思路,以后写出更加优雅springboot拓展代码。