SpringBoot+Mybatis構成Druidマルチデータソース

21286 ワード

冒頭の前に、余談を言います.マルチデータソースと動的データソースの違い.
  • 複数のデータ・ソースは、一般に、複数のトラフィック上の独立したデータベース(異機種化可能なデータベース)に接続するために使用されます.
  • ダイナミックデータソースは、一般的に大規模なアプリケーションでデータを分割するために使用されます.

  • 構成リファレンス
    マルチデータソースを構成する方法については、ネット上のチュートリアルがたくさんあります.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"})を加える.