Springboot は複数のデータ ソース mysql/psql+ を統合して、複数のデータ ソース mybatis-plus がページングを実行できないバグを解決します。
1. springboot は複数のデータソースを統合しています
1.1 依存関係の導入
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
1.2 ymlファイルの設定
次のステップでは、自動構成ではなく、yml ファイルの構成を手動で取得するため、コメントの下にあるさまざまなデータ ソースの名前に必ず注意してください。これら 2 つのデータベースは同時に使用できない可能性があるため、有効なスイッチを追加しました。これが 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 構成パッケージを作成します。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 が提供するマッパー インターフェイスの場所をスキャンします。sqlSessionTemplateRef の名前は任意であり、以降の注釈には影響しませんが、他の構成クラスの名前とは異なる必要があることに注意してください。
2. MysqlDataSourceConfig クラスの各メソッドの前にアノテーションがあり@Primary
、PsqlDataSourceConfig クラスのメソッドは @Qualifier (これらのクラスが異なる限り、ここでの名前も任意です) になります。これは、SpringBoot が自動的にアセンブルされるのを防ぐためです。複数のデータ ソースがあるため、どれを使用すればよいか分からず、起動時にエラーが報告されます。データ ソースが 3 つ以上ある場合は、残りの構成クラス メソッドで PsqlDataSourceConfig のような @Qualifier アノテーションを使用します。
2. mybatis-plusが複数のデータソースでページネーションを実行できないバグを解決
問題:
プロジェクトで単一のデータ ソースを構成する場合、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();
}
複数のデータ ソースの構成は、デフォルトのプロパティ構成を使用するのではなく、コードによって実装する必要があります。ここでは、MybatisSqlSessionFactoryBean を使用して
SqlSessionFactory を構成します。MybatisSqlSessionFactoryBean には、プラグインの構成に使用される setPlugins: というメソッドがあります
。
MybatisSqlSessionFactoryBean Bean = new MybatisSqlSessionFactoryBean(); の使用は、パッケージをスキャンするときに mybatis-plus のマッパーがスキャンできない問題を解決するためです。