前言:本文主要基于springboot项目,从springboot的拓展角度来分析mybatis是如何与springboot集成的。
1、首先搭建springboot集成mybatis环境
1.1、maven pom文件依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>imalvisc-parent</artifactId><groupId>com.imalvisc</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>spring-mybatis</artifactId><dependencies><!-- lombok插件 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- MySQL数据库 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- Druid数据源 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><!-- Mybatis SpringBoot组件 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency></dependencies></project>
1.2、application.yml配置文件
###服务器配置
server:port: 9001spring:application:name: user###druid数据源配置datasource:name: dataSourcetype: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/imalvisc?useSSL=falseusername: rootpassword: imalviscdruid:initial-size: 5min-idle: 5max-active: 20max-wait: 60000time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000validation-query: select 1 from dualtest-while-idle: truetest-on-borrow: falsetest-on-return: falsepool-prepared-statements: truemax-open-prepared-statements: 50max-pool-prepared-statement-per-connection-size: 20filters: stat,wallconnectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000###mybatis数据源配置
mybatis:type-aliases-package: com.imalvisc.spring.mybatis.modelconfiguration: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、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@ConditionalOnMissingBeanpublic 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方法里面的深入逻辑这里就不展开了,需要继续深入剖析的同学可以另查阅资料。
@Overridepublic 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.1Optional.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<String> 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拓展代码。