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();語句,手動回滾,這樣上層就無需去處理異常