SpringBoot+Mybatis構成Druidマルチデータソース
21286 ワード
冒頭の前に、余談を言います.マルチデータソースと動的データソースの違い.複数のデータ・ソースは、一般に、複数のトラフィック上の独立したデータベース(異機種化可能なデータベース)に接続するために使用されます. ダイナミックデータソースは、一般的に大規模なアプリケーションでデータを分割するために使用されます.
構成リファレンス
マルチデータソースを構成する方法については、ネット上のチュートリアルがたくさんあります.SpringBoot+MyBatisマルチデータ・ソースの最もシンプルなソリューションを参照してください.
問題の説明
実際の開発構成では、Druidのファイアウォールモニタリング(WallFilter)と統計モニタリング(StatFilter)を有効にすると、複数の異機種データソースでエラーが発生することがわかりました.エラーメッセージは次のとおりです.
Druidのソースコードを追跡し、問題を発見しました.
すべてのチェック
ソリューション
WallFilterの書き換え
StatFilterの書き換え
フィルタのBeanの設定
同種Bean候補が複数存在する場合、
ふろく
データソース構成クラス
プロファイル
他のデータソース構成の相対的な独立性のために、1つのファイル
トランザクション構成
これは人によって異なり、xml方式でトランザクションを構成するのが好きです.
複数のxmlを1つのxmlにimportし、複雑さを減らすことを目的とします.エントリクラスは注釈
構成リファレンス
マルチデータソースを構成する方法については、ネット上のチュートリアルがたくさんあります.SpringBoot+MyBatisマルチデータ・ソースの最もシンプルなソリューションを参照してください.
問題の説明
実際の開発構成では、Druidのファイアウォールモニタリング(WallFilter)と統計モニタリング(StatFilter)を有効にすると、複数の異機種データソースでエラーが発生することがわかりました.エラーメッセージは次のとおりです.
com.alibaba.druid.sql.parser.ParserException: syntax error, error in....
Druidのソースコードを追跡し、問題を発見しました.
// com.alibaba.druid.wall.WallFilter
private WallCheckResult checkInternal(String sql) throws SQLException {
WallCheckResult checkResult = provider.check(sql);
List violations = checkResult.getViolations();
// ... ...
}
すべてのチェック
sql
は、checkInternal
メソッドで完了し、provider
オブジェクトはinit
初期化を実行した後も変更されなかった.これにより、異機種データベースのsql
チェックが発生します.StatFilter
も同様の問題です.// com.alibaba.druid.filter.stat.StatFilter#createSqlStat(StatementProxy, String)
public JdbcSqlStat createSqlStat(StatementProxy statement, String sql) {
// ...
String dbType = this.dbType;
if (dbType == null) {
dbType = dataSource.getDbType();
}
// ... //
}
ソリューション
WallFilterの書き換え
import com.alibaba.druid.filter.FilterChain;
import com.alibaba.druid.proxy.jdbc.CallableStatementProxy;
import com.alibaba.druid.proxy.jdbc.ConnectionProxy;
import com.alibaba.druid.proxy.jdbc.DataSourceProxy;
import com.alibaba.druid.proxy.jdbc.PreparedStatementProxy;
import com.alibaba.druid.util.JdbcUtils;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.alibaba.druid.wall.WallProvider;
import com.alibaba.druid.wall.spi.DB2WallProvider;
import com.alibaba.druid.wall.spi.MySqlWallProvider;
import com.alibaba.druid.wall.spi.OracleWallProvider;
import com.alibaba.druid.wall.spi.PGWallProvider;
import com.alibaba.druid.wall.spi.SQLServerWallProvider;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Druid
* , WallProvider ,
* @author BBF
* @see com.alibaba.druid.wall.WallFilter
*/
public class FrameWallFilter extends WallFilter {
/**
* ConcurrentHashMap WallProvider
*/
private final Map providerMap = new ConcurrentHashMap<>(8);
/**
* WallProvider
* @param dataSource
* @return WallProvider
*/
private WallProvider getProvider(DataSourceProxy dataSource) {
String dbType;
if (dataSource.getDbType() != null) {
dbType = dataSource.getDbType();
} else {
dbType = JdbcUtils.getDbType(dataSource.getRawJdbcUrl(), "");
}
WallProvider provider;
if (JdbcUtils.MYSQL.equals(dbType) || JdbcUtils.MARIADB.equals(dbType) || JdbcUtils.H2.equals(dbType)) {
provider = providerMap.get(JdbcUtils.MYSQL);
if (provider == null) {
provider = new MySqlWallProvider(new WallConfig(MySqlWallProvider.DEFAULT_CONFIG_DIR));
provider.setName(dataSource.getName());
providerMap.put(JdbcUtils.MYSQL, provider);
}
} else if (JdbcUtils.ORACLE.equals(dbType) || JdbcUtils.ALI_ORACLE.equals(dbType)) {
provider = providerMap.get(JdbcUtils.ORACLE);
if (provider == null) {
provider = new OracleWallProvider(new WallConfig(OracleWallProvider.DEFAULT_CONFIG_DIR));
provider.setName(dataSource.getName());
providerMap.put(JdbcUtils.ORACLE, provider);
}
} else if (JdbcUtils.SQL_SERVER.equals(dbType) || JdbcUtils.JTDS.equals(dbType)) {
provider = providerMap.get(JdbcUtils.SQL_SERVER);
if (provider == null) {
provider = new SQLServerWallProvider(new WallConfig(SQLServerWallProvider.DEFAULT_CONFIG_DIR));
provider.setName(dataSource.getName());
providerMap.put(JdbcUtils.SQL_SERVER, provider);
}
} else if (JdbcUtils.POSTGRESQL.equals(dbType) || JdbcUtils.ENTERPRISEDB.equals(dbType)) {
provider = providerMap.get(JdbcUtils.POSTGRESQL);
if (provider == null) {
provider = new PGWallProvider(new WallConfig(PGWallProvider.DEFAULT_CONFIG_DIR));
provider.setName(dataSource.getName());
providerMap.put(JdbcUtils.POSTGRESQL, provider);
}
} else if (JdbcUtils.DB2.equals(dbType)) {
provider = providerMap.get(JdbcUtils.DB2);
if (provider == null) {
provider = new DB2WallProvider(new WallConfig(DB2WallProvider.DEFAULT_CONFIG_DIR));
provider.setName(dataSource.getName());
providerMap.put(JdbcUtils.DB2, provider);
}
} else {
throw new IllegalStateException("dbType not support : " + dbType);
}
return provider;
}
/**
* provider
* @param connection ConnectionProxy
*/
private void setProvider(ConnectionProxy connection) {
for (Class> cls = this.getClass(); cls != Object.class; cls = cls.getSuperclass()) {
try {
Field field = cls.getDeclaredField("provider");
field.setAccessible(true);
field.set(this, getProvider(connection.getDirectDataSource()));
} catch (Exception e) {
// Field ,
}
}
}
@Override
public PreparedStatementProxy connection_prepareStatement(FilterChain chain,
ConnectionProxy
connection,
String sql) throws SQLException {
this.setProvider(connection);
return super.connection_prepareStatement(chain, connection, sql);
}
@Override
public PreparedStatementProxy connection_prepareStatement(FilterChain chain,
ConnectionProxy connection,
String sql,
int autoGeneratedKeys) throws SQLException {
this.setProvider(connection);
return super.connection_prepareStatement(chain, connection, sql, autoGeneratedKeys);
}
@Override
public PreparedStatementProxy connection_prepareStatement(FilterChain chain,
ConnectionProxy connection,
String sql,
int resultSetType,
int resultSetConcurrency)
throws SQLException {
this.setProvider(connection);
return super.connection_prepareStatement(chain, connection, sql,
resultSetType, resultSetConcurrency);
}
@Override
public PreparedStatementProxy connection_prepareStatement(FilterChain chain,
ConnectionProxy connection,
String sql,
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
this.setProvider(connection);
return super.connection_prepareStatement(chain, connection, sql,
resultSetType, resultSetConcurrency, resultSetHoldability);
}
@Override
public PreparedStatementProxy connection_prepareStatement(FilterChain chain,
ConnectionProxy connection,
String sql,
int[] columnIndexes) throws SQLException {
this.setProvider(connection);
return super.connection_prepareStatement(chain, connection, sql, columnIndexes);
}
@Override
public PreparedStatementProxy connection_prepareStatement(FilterChain chain,
ConnectionProxy connection,
String sql,
String[] columnNames) throws SQLException {
this.setProvider(connection);
return super.connection_prepareStatement(chain, connection, sql, columnNames);
}
@Override
public CallableStatementProxy connection_prepareCall(FilterChain chain,
ConnectionProxy connection,
String sql) throws SQLException {
this.setProvider(connection);
return super.connection_prepareCall(chain, connection, sql);
}
@Override
public CallableStatementProxy connection_prepareCall(FilterChain chain,
ConnectionProxy connection,
String sql,
int resultSetType,
int resultSetConcurrency) throws SQLException {
this.setProvider(connection);
return super.connection_prepareCall(chain, connection, sql,
resultSetType, resultSetConcurrency);
}
@Override
public CallableStatementProxy connection_prepareCall(FilterChain chain,
ConnectionProxy connection,
String sql,
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
this.setProvider(connection);
return super.connection_prepareCall(chain, connection, sql,
resultSetType, resultSetConcurrency, resultSetHoldability);
}
}
StatFilterの書き換え
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.proxy.jdbc.StatementProxy;
import com.alibaba.druid.stat.JdbcSqlStat;
/**
* Druid
* , dbType,
* @author BBF
* @see com.alibaba.druid.filter.stat.StatFilter#createSqlStat(StatementProxy, String)
*/
public class FrameStatFilter extends StatFilter {
@Override
public JdbcSqlStat createSqlStat(StatementProxy statement, String sql) {
super.setDbType(null);
return super.createSqlStat(statement, sql);
}
}
フィルタのBeanの設定
同種Bean候補が複数存在する場合、
@Primary
フラグのBean
に優先される.他の2つの注記@ConfigurationProperties
および@ConditionalOnProperty
は、プロファイルのプレフィックスと特定の属性値がある場合に有効です.
/**
* Druid Bean
* @param wallConfig Bean
* @return WallFilter
* @see com.alibaba.druid.spring.boot.autoconfigure.stat.DruidFilterConfiguration#wallFilter
*/
@Bean("wallFilter")
@ConfigurationProperties("spring.datasource.druid.filter.wall")
@ConditionalOnProperty(prefix = "spring.datasource.druid.filter.wall", name = {"enabled"})
@Primary
public WallFilter wallFilter(@Qualifier("wallConfig") WallConfig wallConfig) {
WallFilter filter = new FrameWallFilter();
filter.setConfig(wallConfig);
return filter;
}
/**
* Druid Bean
* @return StatFilter
* @see com.alibaba.druid.spring.boot.autoconfigure.stat.DruidFilterConfiguration#statFilter
*/
@Bean("statFilter")
@ConfigurationProperties("spring.datasource.druid.filter.stat")
@ConditionalOnProperty(prefix = "spring.datasource.druid.filter.stat", name = {"enabled"}
)
@Primary
public StatFilter statFilter() {
return new FrameStatFilter();
}
ふろく
データソース構成クラス
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.util.JdbcUtils;
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.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import javax.sql.DataSource;
/**
*
* @author BBF
*/
@Configuration
@MapperScan(basePackages = MysqlDataSourceConfig.PACKAGE,
sqlSessionTemplateRef = MysqlDataSourceConfig.SESSION_NAME)
public class MysqlDataSourceConfig {
/**
* Dao
*/
public static final String PACKAGE = "com.bbf.frame.service.dao";
/**
* mapper.xml
*/
private static final String MAPPER_LOCATION = "classpath:/mapperMysql/*Mapper.xml";
/**
* mybatis
*/
private static final String CONFIG_LOCATION = "classpath:/config/mybatis-config.xml";
/**
* bean
*/
private static final String DATASOURCE_NAME = "mysqlDataSource";
private static final String FACTORY_NAME = "mysqlSqlSessionFactory";
public static final String SESSION_NAME = "mysqlSqlSessionTemplate";
@Bean(DATASOURCE_NAME)
@ConfigurationProperties("datasource.druid.mysql")
public DataSource dataSourceTwo() {
DruidDataSource ds= DruidDataSourceBuilder.create().build();
ds.setDbType(JdbcUtils.MYSQL);
return ds;
}
/**
* Mybatis SQL
* @param dataSource
* @return SqlSessionFactory
* @throws Exception SqlSessionFactory
*/
@Bean(name = FACTORY_NAME)
public SqlSessionFactory sqlSessionFactory(@Qualifier(DATASOURCE_NAME) DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resolver.getResources(MAPPER_LOCATION));
sqlSessionFactoryBean.setConfigLocation(resolver.getResource(CONFIG_LOCATION));
return sqlSessionFactoryBean.getObject();
}
@Bean(SESSION_NAME)
public SqlSessionTemplate sqlSessionTemplate(@Qualifier(FACTORY_NAME) SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
プロファイル
他のデータソース構成の相対的な独立性のために、1つのファイル
mysql.properties
として個別に保存される.エントリクラスでは、@PropertySource
が定義され、本明細書では、プライマリ・データ・ソースに加えて、2つのデータ・ソースが定義される.@SpringBootApplication
@ImportResource(locations = {"classpath:config/conf.xml"})
@PropertySource(encoding = "UTF8", value = {"classpath:config/datasource/sqlserver.properties",
"classpath:config/datasource/mysql.properties"})
public class Application {
//
}
############################################
# DataSource - druid Mysql
############################################
# , , dbType, druid WallFilter SQL
# com.alibaba.druid.util.JdbcConstants
datasource.druid.mysql.db-type=mysql
datasource.druid.mysql.driver-class-name=com.mysql.jdbc.Driver
datasource.druid.mysql.url=jdbc:mysql://192.168.1.2:3306/bbf?characterEncoding=UTF-8
datasource.druid.mysql.username=root
datasource.druid.mysql.password=root
#
datasource.druid.mysql.initial-size=5
# 。default=8+
datasource.druid.mysql.max-active=20
# , 。
# maxWait , , 。
# useUnfairLock true
datasource.druid.mysql.max-wait=60000
# prepared statement ,PSCache
# Oracle, poolPreparedStatements true, mysql 5.5 true
datasource.druid.mysql.pool-prepared-statements=true
# PSCache, 0, 0 ,poolPreparedStatements true。
# Druid , Oracle PSCache , , 100。 =-1
datasource.druid.mysql.max-open-prepared-statements=100
# sql, , select 'x'。
# validationQuery null,testOnBorrow,testOnBorrow,testOnReturn,testWhileIdle 。
datasource.druid.mysql.validation-query=SELECT 'V';
# : , 。 jdbc Statement void setQueryTimeout(int seconds)
# mysql , mysql
datasource.druid.mysql.validation-query-timeout=1000
# 。 ,
# : true ,validation-query
datasource.druid.mysql.test-on-borrow=false
#
# : true ,validation-query
datasource.druid.mysql.test-on-return=false
# true, , 。
# , timeBetweenEvictionRunsMillis,
# validationQuery ,validation-query 。default=false
datasource.druid.mysql.test-while-idle=true
# minIdle , minEvictableIdleTimeMillis, keepAlive 。default=false
datasource.druid.mysql.keep-alive=true
# , , default=1
# :
# (1)Destroy , minEvictableIdleTimeMillis
# (2)testWhileIdle , testWhileIdle
datasource.druid.mysql.time-between-eviction-runs-millis=60000
# ,
datasource.druid.mysql.min-evictable-idle-time-millis=100000
datasource.druid.mysql.max-evictable-idle-time-millis=200000
# DruidDataSource
datasource.druid.mysql.use-global-data-source-stat=false
トランザクション構成
これは人によって異なり、xml方式でトランザクションを構成するのが好きです.
複数のxmlを1つのxmlにimportし、複雑さを減らすことを目的とします.エントリクラスは注釈
@ImportResource(locations = {"classpath:config/conf.xml"})
を加える.