1. 程式人生 > >SpringBoot+mybatis+Druid多資料來源切換

SpringBoot+mybatis+Druid多資料來源切換

1. 配置application.properties

#primary db
spring.datasource.primary.url=jdbc:mysql://127.0.0.1:3306/master?characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.primary.filters=stat
spring.datasource.primary.maxActive=20
spring.datasource.primary.initialSize=1 spring.datasource.primary.maxWait=60000 spring.datasource.primary.minIdle=1 spring.datasource.primary.timeBetweenEvictionRunsMillis=60000 spring.datasource.primary.minEvictableIdleTimeMillis=300000 spring.datasource.primary.validationQuery=select 'x' spring.datasource.primary.testWhileIdle=true
spring.datasource.primary.testOnBorrow=false spring.datasource.primary.testOnReturn=false spring.datasource.primary.poolPreparedStatements=true spring.datasource.primary.maxOpenPreparedStatements=20 #slave db1 spring.datasource.secondary.url=jdbc:mysql://127.0.0.1:3306/slave?characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.secondary.username=root spring.datasource.secondary.password=root spring.datasource.secondary.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver spring.datasource.secondary.filters=stat spring.datasource.secondary.maxActive=20 spring.datasource.secondary.initialSize=1 spring.datasource.secondary.maxWait=60000 spring.datasource.secondary.minIdle=1 spring.datasource.secondary.timeBetweenEvictionRunsMillis=60000 spring.datasource.secondary.minEvictableIdleTimeMillis=300000 spring.datasource.secondary.validationQuery=select 'x' spring.datasource.secondary.testWhileIdle=true spring.datasource.secondary.testOnBorrow=false spring.datasource.secondary.testOnReturn=false spring.datasource.secondary.poolPreparedStatements=true spring.datasource.secondary.maxOpenPreparedStatements=20

△這裡注意的是一定要指定主資料來源(主資料來源用primary標識)

2. 配置主資料來源PrimaryDataSourceConfig

@Configuration
@MapperScan(basePackages ="xx.xxx.xx.mapper.primary",sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class PrimaryDataSourceConfig{

    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    @Primary
    public DataSource primaryDataSource() { 
        return new DruidDataSource();
    }

    @Bean(name = "primaryTransactionManager")
    @Primary
    public PlatformTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
         return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "primarySqlSessionFactory")
    @Primary
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource)
    throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setTypeAliasesPackage("xx.xx.xx.model");
        //分頁外掛
        PageHelper pageHelper = new PageHelper();
        Properties properties = new Properties();
        properties.setProperty("reasonable", "true");
        properties.setProperty("supportMethodsArguments", "true");
        properties.setProperty("returnPageInfo", "check");
        properties.setProperty("params", "count=countSql");
        pageHelper.setProperties(properties);
        //新增外掛
        bean.setPlugins(new Interceptor[]{pageHelper});
        //新增XML目錄
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            bean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
            return bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Bean(name = "primarySqlSessionTemplate")
    @Primary
    public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory")
        SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    @Primary
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("primarySqlSessionFactory");
        mapperScannerConfigurer.setBasePackage("xx.xx.xx.mapper.primary");
        Properties properties = new Properties();
        properties.setProperty("mappers", "xx.xx.xx.util.MyMapper");
        properties.setProperty("notEmpty", "false");
        properties.setProperty("IDENTITY", "MYSQL");
        mapperScannerConfigurer.setProperties(properties);
        return mapperScannerConfigurer;
    }
}

△多個數據源要標識哪一個為主資料來源所以這裡PrimaryDataSourceConfig設定為主資料來源需要用@primary來標識是主資料來源(預設使用主資料來源)

3.配置次資料來源SlaveOneDataSourceConfig

@Configuration
@MapperScan(basePackages ="xx.xx.xx.mapper.slave1",sqlSessionTemplateRef = "slaveOneSqlSessionTemplate")
public class SlaveOneDataSourceConfig{

    @Bean(name = "slaveOneDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource slaveOneDataSource() {
        return new DruidDataSource();
    }

    @Bean(name = "slaveOneTransactionManager")
    public PlatformTransactionManager slaveOneTransactionManager(@Qualifier("slaveOneDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "slaveOneSqlSessionFactory")
    public SqlSessionFactory slaveOneSqlSessionFactory(@Qualifier("slaveOneDataSource") DataSource dataSource) 
    throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setTypeAliasesPackage("xx.xx.xx.model");
        //分頁外掛
        PageHelper pageHelper = new PageHelper();
        Properties properties = new Properties();
        properties.setProperty("reasonable", "true");
        properties.setProperty("supportMethodsArguments", "true");
        properties.setProperty("returnPageInfo", "check");
        properties.setProperty("params", "count=countSql");
        pageHelper.setProperties(properties);
        //新增外掛
        bean.setPlugins(new Interceptor[]{pageHelper});
        //新增XML目錄
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            bean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
            return bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Bean(name = "slaveOneSqlSessionTemplate")
    public SqlSessionTemplate slaveOneSqlSessionTemplate(@Qualifier("slaveOneSqlSessionFactory")
    SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("slaveOneSqlSessionFactory");
        mapperScannerConfigurer.setBasePackage("cn.ctx.admin.mapper.slave1");
        Properties properties = new Properties();
        properties.setProperty("mappers", "xx.xx.xx.util.MyMapper");
        properties.setProperty("notEmpty", "false");
        properties.setProperty("IDENTITY", "MYSQL");
        mapperScannerConfigurer.setProperties(properties);
        return mapperScannerConfigurer;
    }
}

@MapperScan(basePackages =”cn.ctx.admin.mapper”,sqlSessionTemplateRef = “sqlSessionTemplate”)
basePackages 對應的dao mybatis會根據不同的dao切換資料來源

4. 多資料來源使用事物

  • 程式入口需要加入 @EnableTransactionManagement 註解開啟註解,等同於xml配置方式的 <tx:annotation-driven />
   //△多資料來源 value 指定需要回滾的資料來源一定要寫
   @Transactional(value="slaveOneTransactionManager")
   @Override
   public Map<String, Object> addSlaveData() {
       Map<String,Object> map=new HashMap<String,Object>();
       try {
           int i=userInfoMapper.addSlaveData();
           if(i>0) {
               map.put("result","寫入成功");
           }else {
               map.put("result","寫入失敗");
           }
       }catch (Exception e) {
           map.put("result","寫入失敗");
           //異常手動回滾,好處上層就無需去處理異常
           TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
       }
       return map;
   }

△注意,發現事務不回滾
原因:
- 預設spring事務只在發生未被捕獲的 RuntimeException 時才回滾。
- spring aop 異常捕獲原理:被攔截的方法需顯式丟擲異常,並不能經任何處理,這樣aop代理才能捕獲到方法的異常,才能進行回滾,預設情況下aop只捕獲 RuntimeException 的異常,但可以通過配置來捕獲特定的異常並回滾
換句話說在service的方法中不使用try catch 或者在catch中最後加上throw new runtimeexcetpion(),這樣程式異常時才能被aop捕獲而回滾
- 解決方案:
- 方案1.例如service層處理事務,那麼service中的方法中不做異常捕獲,或者在catch語句中最後增加throw new RuntimeException()語句,以便讓aop捕獲異常再去回滾,並且在service上層(webservice客戶端,view層action)要繼續捕獲這個異常並處理
- 方案2.在service層方法的catch語句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();語句,手動回滾,這樣上層就無需去處理異常