学无先后,达者为师

网站首页 编程语言 正文

mybatis源码之集成springboot原理

作者:xuguofeng2016 更新时间: 2022-07-22 编程语言

本文将结合示例代码、阅读源码,去深入了解mybatis与spring boot集成的底层原理。

示例代码

示例展示最小化配置。

依赖

只记录相关内容:

<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.1.4</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.44</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.20</version>
</dependency>

application.yml配置

只记录相关内容:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_source_analysis
    username: root
    password: 123456
    druid:
      filters: stat
      maxActive: 5
      initialSize: 5
      maxWait: 60000
      minIdle: 1
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      maxOpenPreparedStatements: 5
# 此处是mybatis自动装配的最小化配置
# 如果在mapper接口上使用注解方式编写SQL的话,这个配置也是不需要的
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml

mapper xml配置文件

与之前一样,不记录。

MybatisConfig配置类

@MapperScan(basePackages = {"org.net5ijy.mybatis.test.mapper"})
@Configuration
public class MybatisConfig {
  // 由于DataSource、SqlSessionFactory和SqlSessionTemplate由spring boot mybatis自动装配,这个类可以空白
}

Mapper接口

与之前一样,不记录。

接口层编写

只记录相关内容:

@RestController
@RequestMapping("/blog")
public class BlogController {

  private BlogMapper blogMapper;

  public BlogController(BlogMapper blogMapper) {
    this.blogMapper = blogMapper;
  }

  // other code
}

启动类

@SpringBootApplication
public class MybatisSpringBootApplication {

  public static void main(String[] args) {
    SpringApplication.run(MybatisSpringBootApplication.class, args);
  }
}

核心原理

我们依赖的mybatis-spring-boot-starter包,而这个包没有实际代码和配置文件,只是引入了mybatis、mybatis-spring、和mybatis-spring-boot-autoconfigure等包。

自动装配的实现在mybatis-spring-boot-autoconfigure包里面。

我们知道spring boot的自动装配原理是在自动装配包的META-INF下创建spring.factories文件,在文件里面配置EnableAutoConfiguration的配置类全名即可实现自动装配,我们看一下mybatis-spring-boot-autoconfigure包的spring.factories文件:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

核心内容就在MybatisAutoConfiguration这个类。

所以这个类会帮助我们创建SqlSessionFactory和SqlSessionTemplate,而DataSource是由DataSourceAutoConfiguration类来做的(这个暂不讨论)。

所以应该从MybatisAutoConfiguration为入口开始分析。

其实基本上所有的第三方库的集成都是这样的原理:

  • 一个starter库
  • 一个autoconfigure库
  • 一个spring.factories文件,其实这个才是最重要的。至于是一个包还是两个包,都问题不大,可以实现就行
  • 一个AutoConfiguration类
  • 一个Properties类

至于spring.factories文件被加载的原理,这是spring boot的内容,本文不做扩展分析。

源码分析

MybatisAutoConfiguration类

这个类负责创建SqlSessionFactory和SqlSessionTemplate对象。

看一下这个类的成员和构造方法:

public class MybatisAutoConfiguration implements InitializingBean {

  private final MybatisProperties properties;
  private final Interceptor[] interceptors;
  private final TypeHandler[] typeHandlers;
  private final LanguageDriver[] languageDrivers;
  private final ResourceLoader resourceLoader;
  private final DatabaseIdProvider databaseIdProvider;
  private final List<ConfigurationCustomizer> configurationCustomizers;

  public MybatisAutoConfiguration(
      MybatisProperties properties, // 用户在application.yml里面的配置参数,后续详解
      // 容器里面的Interceptor拦截器,就是说使用@Component标注的Interceptor实现类对象都会在这里,
      // 这是spring的依赖注入特性,
      // 这种机制更便于组件扩展
      ObjectProvider<Interceptor[]> interceptorsProvider,
      // 容器里面的TypeHandler集合,原理同上
      ObjectProvider<TypeHandler[]> typeHandlersProvider,
      ObjectProvider<LanguageDriver[]> languageDriversProvider,
      ResourceLoader resourceLoader,
      ObjectProvider<DatabaseIdProvider> databaseIdProvider,
      // 这个也是一种扩展机制,让用户获取到Configuration对象,然后做操作
      ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
    // 成员变量赋值
  }

创建SqlSessionFactory

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  // 之前与spring集成时做过分析
  SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  factory.setDataSource(dataSource);
  factory.setVfs(SpringBootVFS.class);
  // 设置mybatis.configLocation配置
  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);
  }
  // mapper xml扫描
  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();
}

private void applyConfiguration(SqlSessionFactoryBean factory) {
  Configuration configuration = this.properties.getConfiguration();
  if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
    configuration = new Configuration();
  }
  if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
    // 这里在调用用户的ConfigurationCustomizer自定义扩展逻辑
    for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
      customizer.customize(configuration);
    }
  }
  factory.setConfiguration(configuration);
}

创建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);
  }
}

代码比较简单,就是创建SqlSessionTemplate对象。

MybatisProperties配置类

MybatisProperties配置类封装了用户的所有mybatis配置,这个类读取mybatis.开始的配置参数,封装到自己的属性中。

类定义:

@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties {
  // ...
}

支持的配置:
在这里插入图片描述

在这里插入图片描述

从图中可以看到,这类本身支持的配置并不是很多:

mapperLocations - mapper xml配置文件位置
checkConfigLocation - 是否对主配置文件进行检查,默认false不检查
configLocation - 主配置文件位置
configurationProperties - 主配置扩展属性
defaultScriptingLanguageDriver - 默认的脚本语言模板类
executorType - SqlSessionTemplate的运行模式
typeAliases* - 类型别名
typeHandlersPackage - 类型处理器

其余的以mybatis.configuration.开始的配置封装在MybatisProperties的Configuration对象中,这个对象封装mybatis的核心配置,属性较多。

以mybatis.scripting-language-driver.开始的配置是脚本语言模板类的属性,本文不介绍。

@MapperScan注解原理(略)

在 mybatis源码分析_集成spring源码分析 文中有详细分析,此处略。

插件加载原理

三种方式

  • 使用@Component标注Interceptor实现类
  • 使用ConfigurationCustomizer的customize方法向Configuration addInterceptor
  • 编写mybatis主配置文件,使用plugins标签注册

@Component标注Interceptor实现类

在之前分析MybatisAutoConfiguration时看过构造方法,其中让spring注入了一个ObjectProvider<Interceptor[]>对象,这个对象里面是容器里面所有的Interceptor接口的实例对象。

ObjectProvider接口是spring 4.3提供的新特性,是ObjectFactory接口的扩展,专门为注入点设计的,可以让注入变得更加宽松和更具有可选项。

如果待注入参数的bean为空或有多个时,ObjectProvider就会起作用:

  • 如果注入实例为空时,使用ObjectProvider则避免了强依赖导致的依赖对象不存在异常
  • 如果有多个实例,ObjectProvider的方法会根据bean实现的Ordered接口或@Order注解指定的先后顺序获取一个Bean。从而了提供了一个更加宽松的依赖注入方式

所以就可以理解这种方法了。

所以不只是使用@Component标注Interceptor实现类,在Configuration配置类中使用@Bean创建bean方式也是可行的。

@Bean
public PrintSqlInterceptor printSqlInterceptor() {
  return new PrintSqlInterceptor();
}

ConfigurationCustomizer的customize方法

MybatisAutoConfiguration的构造方法还注入了ObjectProvider<List<ConfigurationCustomizer>>对象,其原理和前文一样,我们需要了解的是ConfigurationCustomizer接口:

@FunctionalInterface
public interface ConfigurationCustomizer {

  void customize(Configuration configuration);
}

这是一个函数接口,customize方法接收一个mybatis的Configuration对象作为参数,可以使用Configuration的addInterceptor(Interceptor)方法注册插件。

其实不止是插件,其他操作Configuration对象的需求也可以使用这个接口来实现。

原文链接:https://blog.csdn.net/xuguofeng2016/article/details/125919307

栏目分类
最近更新