Mybatis应用篇(一): 自定义配置

1,644 阅读4分钟

前言


使用springboot 整合 mybatis时,经常进行相关参数设置,尤其是自定义配置SqlSessionFactoryBean 时,需要将配置参数放到配置中心进行灵活配置。在公司日常项目中发现很多项目在apollo上添加了参数但仍然没有生效,然后只能在配置类中写死。秉着程序员遇到问题不要怕,借着解决问题的机会学习进步的想法研究了一下。因此将mybatis一些常见的配置方式介绍一下。

配置 SqlSessionFactoryBean

常见配置(笨重)

自定义SqlSessionFactoryBean 应该是最常见的场景了,下面来见一个最简单的配置


SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setTypeAliasesPackage("com.module.mybatispratice.po");
// classpath 当前类加载器路径  classpath* 所有类加载器路径
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:/templates/mappers/*.xml");
factoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath*:mybatis-config.xml"));
factoryBean.setMapperLocations(resources);

然后在yml文件或 apollo配置中心上加一堆参数:

## mybatis 相关配置
mybatis:
    configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        map-underscore-to-camel-case: true
#        local-cache-scope: statement
        cache-enabled: true
    mapper-locations: classpath*:templates/mappers/*.xml
    type-aliases-package: com.module.mybatispratice.po

在使用过程中会发现各种问题,比如数据库结果集自动驼峰转换用不了(mybatis高版本驼峰已经自动开启了),就会觉得很奇怪,明明已经加了配置但没有效果。

那我们分析上上述原因,在springboot项目中,即使有mybatis-config.xml这个文件实际上也很少使用,也就是这种写法实际上只配置了DataSource ,TypeAliasesPackage,MapperLocations 这几个参数。

为什么明明加了配置,缺没有生效呢?

这是因为很多参数项跟 SqlSessionFactoryBean 里的属性看着是一致,你以为会直接自动注入进去,实际上并不会。
比如添加了 mybatis.configuration.map-underscore-to-camel-case ,但当我们在yml文件中直接点击配置项进入源代码时 (**点击yml文件中配置可以直接进入到配置使用的地方!!!**) ,我们可以发现这是对应 org.apache.ibatis.session.Configuration#mapUnderscoreToCamelCase这个属性,也就是设置到 Configuration对象的 mapUnderscoreToCamelCase属性上,而并没有直接作用到SqlSessionFactoryBean 上,因此我们需要将 Configuration对象设置到 SqlSessionFactoryBean 才能生效。

比如下面这样

初步优化配置

@Bean
@Primary
public SqlSessionFactoryBean factoryBean(org.apache.ibatis.session.Configuration config) throws IOException {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource);
    factoryBean.setConfiguration(config);
    factoryBean.setTypeAliasesPackage("com.module.mybatispratice.po");
    // classpath 当前类加载器路径  classpath* 所有类加载器路径
    Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:/templates/mappers/*.xml");
    factoryBean.setMapperLocations(resources);

    return factoryBean;
}

@Bean
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration config() {

    return new org.apache.ibatis.session.Configuration();
}

上面的方式基本上实现了将大部分参数都设置进去了,但眼尖地朋友肯定已经发现了。有几个重要参数,还是通过手写的方式设置的,setMapperLocations ,setTypeAliasesPackage

为什么 mybatis.configuration.mapper-locations 没有直接设置到org.mybatis.spring.SqlSessionFactoryBean#mapperLocations上呢?

我们先看下 mybatis.configuration.mapper-locations 到底是给哪个bean设置属性? 原来是给 org.mybatis.spring.boot.autoconfigure.MybatisProperties#mapperLocations使用。 你会发现,两个属性名字虽然一样,且都是数组,但类型不一样,一个是字符串数组,一个是 Resource数组。看到这里就应该差不多清楚了,肯定不是直接设置上去的,中间有个转换。没错,org.mybatis.spring.boot.autoconfigure.MybatisProperties#resolveMapperLocations 就是用来转换的。

其他还有个别参数也是同样地道理,那我们如何避免漏掉参数呢?

一般的参数都是放在setConfiguration , SqlSessionFactoryBean 额外暴露出来的设置参数属性方法就是我们需要再进行设置的。

推荐设置

//以下三种方式都可以自动将 Interceptor 接口的全部实现类的实例注入进来

@Autowired
private Interceptor[] interceptors;

@Autowired
private List<Interceptor> interceptorList;

@Autowired
private Map<String,Interceptor> interceptorMap;

@Bean
@Primary
public SqlSessionFactoryBean factoryBean(MybatisProperties mybatisProperties) throws Exception {
    //自定义 SqlSessionFactoryBean 一般提供set方法的都是需要额外再自行设置的 ,否则直接装到 configuration 对象里了
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setConfiguration(mybatisProperties.getConfiguration());
    factoryBean.setDataSource(dataSource);
    factoryBean.setMapperLocations(mybatisProperties.resolveMapperLocations());
    factoryBean.setTypeAliasesPackage(mybatisProperties.getTypeAliasesPackage());
    //yml文件中的很多参数看着跟 SqlSessionFactoryBean 中的属性名一致,可是如果是自定义了 SqlSessionFactoryBean ,实际上是无法直接通过属性赋值的
    //可以借用 MybatisProperties 来进行设值

    //将全部拦截器设置进来
    factoryBean.setPlugins(interceptors);
    return factoryBean;
}

一个官方设置模板(推荐)

mybatis-spring-boot-autoconfigure-2.2.2.jar 中的 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration 就是一个很好地实践方案。 观察这个方法,你可以很清晰地知道参数如何设置进去,哪些是还需要额外进行设置的。

@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);
  }
  applySqlSessionFactoryBeanCustomizers(factory);
  return factory.getObject();
}