Springboot integrates multiple data sources mysql/psql+ to solve the bug that multiple data sources mybatis-plus cannot perform paging
1. springboot is integrating multiple data sources
1.1 Introducing dependencies
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
1.2 Configure the yml file
Be sure to pay attention to the names of the different data sources below the comments, because the next step is to manually obtain the configuration of the yml file, not automatic configuration. Because these two databases may not be used at the same time, I added the enabled switch. If it is true, the database configuration will be loaded
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 Create a config package, as long as SpringBoot scans it, there is no need to add special comments on the startup class
Related configuration of 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;
}
load 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 related configuration
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
Inside basePackages
is to scan the location of the mapper interface provided by MyBatis. The name in sqlSessionTemplateRef is arbitrary and has no effect on the following annotations, but note that it should be different from the names of the other configuration classes.
2. There are annotations in front of each method of the MysqlDataSourceConfig class @Primary
, and the method of the PsqlDataSourceConfig class becomes @Qualifier (the name here is also arbitrary, as long as these classes are different), this is to prevent SpringBoot from automatically assembling. Multiple data sources, do not know which one to use and report an error at startup. If there are more than two data sources, use the @Qualifier annotation like PsqlDataSourceConfig on the rest of the configuration class methods.
2. Solve the bug that multiple data sources mybatis-plus cannot perform pagination
Problem:
When the project configures a single data source, the mybatis-plus paging plugin works normally. When configuring multiple data sources, the mybatis-plus paging plugin fails.
2.1 Inject mybatis-plus paging plugin
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setDialectType(DbType.POSTGRE_SQL.getDb());
return paginationInterceptor;
}
2.2 Configuration under multiple data sources
@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();
}
The configuration of multiple data sources needs to be implemented by code instead of using the default properties configuration. Here I use
MybatisSqlSessionFactoryBean to configure SqlSessionFactory, and MybatisSqlSessionFactoryBean just has a method that is
setPlugins: used to configure plugins.
The use of MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); is to solve the problem that the mapper of mybatis-plus cannot be scanned when scanning packages.