前言
使用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();
}