一、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