springboot在集成多数据源 mysql/psql+ 解决多数据源mybatis-plus无法进行分页的BUG
1.springboot在集成多数据源
1.1引入依赖
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
1.2配置yml文件
其中请务必关注注释下面的不同的数据源的名字,因为下一步是手动获取yml文件的配置,并非自动配置。因为不一定会同时使用这两个数据库,所以我加了enabled开关,如果为true,才会加载数据库配置
spring:
#mysql数据源
mysqldb:
enabled: false
driver-class-name: com.mysql.cj.jdbc.Driver
password: root
url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&useUnicode=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useAffectedRows=true&useSSL=false
username: root
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
filters: stat,wall,log4j
initialSize: 5
maxActive: 20
maxPoolPreparedStatementPerConnectionSize: 20
maxWait: 60000
minEvictableIdleTimeMillis: 300000
minIdle: 5
poolPreparedStatements: true
testOnBorrow: false
testOnReturn: false
testWhileIdle: true
timeBetweenEvictionRunsMillis: 60000
validationQuery: SELECT 1 FROM DUAL
#psql数据源
psqldb:
enabled: true
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://127.0.0.1:9543/test
username: postgres
password: postgres
# 初始化大小,最小,最大
initialSize: 1
minIdle: 3
maxActive: 200
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 40000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
1.3创建config包,这个只要让SpringBoot扫描到就行,并不需要在启动类上特别加注释
mysql的相关配置
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@Component
@Data
@ConfigurationProperties(prefix = "spring.mysqldb")
public class MysqlDataSourceProperties implements Serializable {
private String driverClassName;
private String url;
private String username;
private String password;
private String connectionProperties;
private String filters;
private Integer initialSize;
private Integer maxActive;
private Integer maxPoolPreparedStatementPerConnectionSize;
private Integer maxWait;
private Integer minEvictableIdleTimeMillis;
private Integer minIdle;
private boolean poolPreparedStatements;
private boolean testOnBorrow;
private boolean testOnReturn;
private boolean testWhileIdle;
private Integer timeBetweenEvictionRunsMillis;
private String validationQuery;
private boolean enabled;
}
加载MySQL
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.sql.DataSource;
import java.util.List;
@Configuration
## ConditionalOnProperty指定enabled开启时,才会加载该数据库相关信息
@ConditionalOnProperty(prefix = "spring.mysqldb", name = "enabled", havingValue = "true")
@MapperScan(basePackages = "com.koal.ipsec.mapper", sqlSessionFactoryRef = "mysqlSqlSessionFactory")
public class MysqlDataSourceConfig {
@Autowired
private MysqlDataSourceProperties mysqlDataSourceProperties;
@Bean(name = "mysqlDataSource")
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(mysqlDataSourceProperties.getDriverClassName());
dataSource.setUrl(mysqlDataSourceProperties.getUrl());
dataSource.setUsername(mysqlDataSourceProperties.getUsername());
dataSource.setPassword(mysqlDataSourceProperties.getPassword());
dataSource.setInitialSize(mysqlDataSourceProperties.getInitialSize());
dataSource.setMinIdle(mysqlDataSourceProperties.getMinIdle());
dataSource.setMaxActive(mysqlDataSourceProperties.getMaxActive());
return dataSource;
}
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setDialectType(DbType.MYSQL.getDb());
return paginationInterceptor;
}
@Bean(name = "mysqlSqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
##注意: Mybatis plus分页插件配置必须使用MybatisSqlSessionFactoryBean ,否则分页失效
## MybatisSqlSessionFactoryBean 是为了解决扫包的时候无法扫描mybatis-plus的mapper的问题。
final MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
## 重点
MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
mybatisSqlSessionFactoryBean.setConfiguration(mybatisConfiguration);
mybatisSqlSessionFactoryBean.setDataSource(dataSource);
mybatisSqlSessionFactoryBean.setPlugins(new Interceptor[]{
paginationInterceptor()});
## 指定enum类的扫描
mybatisSqlSessionFactoryBean.setTypeEnumsPackage("com.test.constant");
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resourcePatternResolver.getResources("classpath*:mapper/*Mapper.xml");
mybatisSqlSessionFactoryBean.setMapperLocations(resources);
return mybatisSqlSessionFactoryBean.getObject();
}
@Bean(name = "mysqlSqlSessionTemplate")
@Primary
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("mysqlSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean(name = "mysqlTransactionManager")
public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("mysqlDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
psql相关配置
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@Component
@Data
@ConfigurationProperties(prefix = "spring.psqldb")
public class PsqlDataSourceProperties implements Serializable {
private String driverClassName;
private String url;
private String username;
private String password;
private Integer initialSize;
private Integer maxActive;
private Integer maxWait;
private Integer minIdle;
private Integer timeBetweenEvictionRunsMillis;
private Integer minEvictableIdleTimeMillis;
private String validationQuery;
private boolean testOnBorrow;
private boolean testOnReturn;
private boolean testWhileIdle;
}
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
@ConditionalOnProperty(prefix = "spring.psqldb", name = "enabled", havingValue = "true")
@MapperScan(basePackages = "com.koal.ipsec.mapper", sqlSessionFactoryRef = "psqlSqlSessionFactory")
public class PsqlDataSourceConfig {
@Autowired
private PsqlDataSourceProperties psqlDataSourceProperties;
@Bean(name = "psqlDataSource")
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(psqlDataSourceProperties.getDriverClassName());
dataSource.setUrl(psqlDataSourceProperties.getUrl());
dataSource.setUsername(psqlDataSourceProperties.getUsername());
dataSource.setPassword(psqlDataSourceProperties.getPassword());
dataSource.setInitialSize(psqlDataSourceProperties.getInitialSize());
dataSource.setMinIdle(psqlDataSourceProperties.getMinIdle());
dataSource.setMaxActive(psqlDataSourceProperties.getMaxActive());
return dataSource;
}
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setDialectType(DbType.POSTGRE_SQL.getDb());
return paginationInterceptor;
}
@Bean(name = "psqlSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("psqlDataSource") DataSource dataSource) throws Exception {
final MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
mybatisSqlSessionFactoryBean.setConfiguration(mybatisConfiguration);
mybatisSqlSessionFactoryBean.setDataSource(dataSource);
mybatisSqlSessionFactoryBean.setPlugins(new Interceptor[]{
paginationInterceptor()});
// mybatisSqlSessionFactoryBean.setPlugins(new Interceptor[]{
psqlMybatisPlusConfig.paginationInterceptor()});
mybatisSqlSessionFactoryBean.setTypeEnumsPackage("com.koal.ipsec.constant");
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resourcePatternResolver.getResources("classpath*:mapper/*Mapper.xml");
mybatisSqlSessionFactoryBean.setMapperLocations(resources);
return mybatisSqlSessionFactoryBean.getObject();
}
@Bean(name = "psqlSqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("psqlSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean(name = "psqlTransactionManager")
public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("psqlDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
1.MapperScan
内的basePackages
是扫描MyBatis提供的mapper接口所在位置,sqlSessionTemplateRef 里面随意起名,对下面的注解没有影响,但是注意,要和其余几个配置类所起的名字要不同。
2.MysqlDataSourceConfig 类每个方法前面都有@Primary
注解,而PsqlDataSourceConfig 类方法就变成了@Qualifier(这里面的名字也是随意,只要这几个类不同就行),这个是防止SpringBoot自动装配的时候,发现存在多个数据源,不知道该使用哪个而在启动时报错。 如有两个以上的数据源,就在其余的配置类的方法上和PsqlDataSourceConfig 一样使用@Qualifier注解。
2.解决多数据源mybatis-plus无法进行分页的BUG
问题:
项目配置单数据源的时候,mybatis-plus分页插件正常使用,配置多数据源的时候,mybatis-plus分页插件失效。
2.1注入mybatis-plus分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setDialectType(DbType.POSTGRE_SQL.getDb());
return paginationInterceptor;
}
2.2多数据源下的配置
@Bean(name = "mysqlSqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
##注意: Mybatis plus分页插件配置必须使用MybatisSqlSessionFactoryBean ,否则分页失效
## MybatisSqlSessionFactoryBean 是为了解决扫包的时候无法扫描mybatis-plus的mapper的问题。
final MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
## 重点
MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
mybatisSqlSessionFactoryBean.setConfiguration(mybatisConfiguration);
mybatisSqlSessionFactoryBean.setDataSource(dataSource);
mybatisSqlSessionFactoryBean.setPlugins(new Interceptor[]{
paginationInterceptor()});
## 指定enum类的扫描
mybatisSqlSessionFactoryBean.setTypeEnumsPackage("com.test.constant");
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resourcePatternResolver.getResources("classpath*:mapper/*Mapper.xml");
mybatisSqlSessionFactoryBean.setMapperLocations(resources);
return mybatisSqlSessionFactoryBean.getObject();
}
多数据源的配置需要用代码实现,而不是使用默认的properties配置。这里我用了
MybatisSqlSessionFactoryBean配置SqlSessionFactory,而MybatisSqlSessionFactoryBean刚好有个方法就是
setPlugins:用于配置插件。
使用MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();是为了解决扫包的时候无法扫描mybatis-plus的mapper的问题。