前段时间在做会员中心和中间件零碎开发时,屡次碰到多数据源配置问题,次要用到分包形式、参数化切换、注解 +AOP、动静增加 这四种形式。这里做一下总结,分享下应用心得以及踩过的坑。
分包形式
数据源配置文件
在 yml 中,配置两个数据源,id 别离为 master 和 s1。
spring:
datasource:
master:
jdbcUrl: jdbc:mysql://192.168.xxx.xxx:xxxx/db1?.........
username: xxx
password: xxx
driverClassName: com.mysql.cj.jdbc.Driver
s1:
jdbcUrl: jdbc:mysql://192.168.xxx.xxx:xxxx/db2?........
username: xxx
password: xxx
driverClassName: com.mysql.cj.jdbc.Driver
数据源配置类
master 数据源配置类
留神点:
须要用 @Primary 注解指定默认数据源,否则 spring 不晓得哪个是主数据源;
@Configuration
@MapperScan(basePackages = "com.hosjoy.xxx.xxx.xxx.xxx.mapper.master", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataSourceConfig { // 默认数据源
@Bean(name = "masterDataSource")
@Primary @ConfigurationProperties(prefix = "spring.datasource.master")
public HikariDataSource masterDataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build();} @Bean(name = "masterSqlSessionFactory")
@Primary public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource datasource, PaginationInterceptor paginationInterceptor)
throws Exception {MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); bean.setDataSource(datasource); bean.setMapperLocations( // 设置 mybatis 的 xml 所在位置
new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/master/**/**.xml"));
bean.setPlugins(new Interceptor[]{paginationInterceptor});
return bean.getObject();} @Bean(name = "masterSqlSessionTemplate")
@Primary public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sessionfactory) {return new SqlSessionTemplate(sessionfactory);
}}
s1 数据源配置类
@Configuration
@MapperScan(basePackages = "com.hosjoy.xxx.xxx.xxx.xxx.mapper.s1", sqlSessionFactoryRef = "s1SqlSessionFactory")
public class S1DataSourceConfig {@Bean(name = "s1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.s1")
public HikariDataSource s1DataSource() {return DataSourceBuilder.create().type(HikariDataSource.class).build();} @Bean(name = "s1SqlSessionFactory")
public SqlSessionFactory s1SqlSessionFactory(@Qualifier("s1DataSource") DataSource datasource
, PaginationInterceptor paginationInterceptor) throws Exception {MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); bean.setDataSource(datasource); bean.setMapperLocations( // 设置 mybatis 的 xml 所在位置
new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/s1/**/**.xml"));
bean.setPlugins(new Interceptor[]{paginationInterceptor});
return bean.getObject();}
@Bean(name = "s1SqlSessionTemplate")
public SqlSessionTemplate s1SqlSessionTemplate(@Qualifier("s1SqlSessionFactory") SqlSessionFactory sessionfactory) {return new SqlSessionTemplate(sessionfactory);
}
}
应用
能够看出,mapper 接口、xml 文件,须要依照不同的数据源分包。在操作数据库时,依据须要在 service 类中注入 dao 层。
特点剖析
长处
实现起来简略,只须要编写数据源配置文件和配置类,mapper 接口和 xml 文件留神分包即可。
毛病
很显著,如果前面要减少或删除数据源,不仅要批改数据源配置文件,还须要批改配置类。
例如减少一个数据源,同时还须要新写一个该数据源的配置类,同时还要思考新建 mapper 接口包、xml 包等,没有实现“热插拔”成果。
参数化切换形式
思维
参数化切换数据源,意思是,业务侧须要依据以后业务参数,动静的切换到不同的数据源。
这与分包思维不同。分包的前提是在编写代码的时候,就曾经晓得以后须要用哪个数据源,而参数化切换数据源须要依据业务参数决定用哪个数据源。
例如,申请参数 userType 值为 1 时,须要切换到数据源 slave1;申请参数 userType 值为 2 时,须要切换到数据源 slave2。
/** 伪代码 **/
int userType = reqUser.getType();
if (userType == 1){
// 切换到数据源 slave1
// 数据库操作
} else if(userType == 2){
// 切换到数据源 slave2
// 数据库操作
}
设计思路
数据源注册
数据源配置类创立 datasource 时,从 yml 配置文件中读取所有数据源配置,主动创立每个数据源,并注册至 bean 工厂和 AbstractRoutingDatasource(前面聊聊这个),同时返回默认的数据源 master。
数据源切换
(1)通过线程池解决申请,每个申请独占一个线程,这样每个线程切换数据源时互不影响。
(2)依据业务参数获取应切换的数据源 ID,依据 ID 从数据源缓存池获取数据源 bean;
(3)生成以后线程数据源 key;
(4)将 key 设置到 threadLocal;
(5)将 key 和数据源 bean 放入数据源缓存池;
(6)在执行 mapper 办法前,spring 会调用 determineCurrentLookupKey 办法获取 key,而后依据 key 去数据源缓存池取出数据源,而后 getConnection 获取该数据源连贯;
(7)应用该数据源执行数据库操作;
(8)开释以后线程数据源。
AbstractRoutingDataSource 源码剖析
spring 为咱们提供了 AbstractRoutingDataSource 抽象类,该类就是实现动静切换数据源的要害。
咱们看下该类的类图,其实现了 DataSource 接口。
咱们看下它的 getConnection 办法的逻辑,其首先调用 determineTargetDataSource 来获取数据源,再获取数据库连贯。很容易猜想到就是这里来决定具体应用哪个数据源的。
进入到 determineTargetDataSource 办法,咱们能够看到它先是调用 determineCurrentLookupKey 获取到一个 lookupKey,而后依据这个 key 去 resolvedDataSources 里去找相应的数据源。
看下该类定义的几个对象,defaultTargetDataSource 是默认数据源,resolvedDataSources 是一个 map 对象,存储所有主从数据源。
所以,要害就是这个 lookupKey 的获取逻辑,决定了以后获取的是哪个数据源,而后执行 getConnection 等一系列操作。determineCurrentLookupKey 是 AbstractRoutingDataSource 类中的一个形象办法,而它的返回值是你所要用的数据源 dataSource 的 key 值,有了这个 key 值,resolvedDataSource(这是个 map, 由配置文件中设置好后存入的)就从中取出对应的 DataSource,如果找不到,就用配置默认的数据源。
所以,通过扩大 AbstractRoutingDataSource 类,并重写其中的 determineCurrentLookupKey() 办法,能够实现数据源的切换。
代码实现
上面贴出要害代码实现。
数据源配置文件
这里配了 3 个数据源,其中主数据源是 MySQL,两个从数据源是 sqlserver。
spring:
datasource:
master:
jdbcUrl: jdbc:mysql://192.168.xx.xxx:xxx/db1?........
username: xxx
password: xxx
driverClassName: com.mysql.cj.jdbc.Driver
slave1:
jdbcUrl: jdbc:sqlserver://192.168.xx.xxx:xxx;DatabaseName=db2
username: xxx
password: xxx
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
slave2:
jdbcUrl: jdbc:sqlserver://192.168.xx.xxx:xxx;DatabaseName=db3
username: xxx
password: xxx
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
定义动静数据源
次要是继承 AbstractRoutingDataSource,实现 determineCurrentLookupKey 办法。
public class DynamicDataSource extends AbstractRoutingDataSource {
/* 存储所有数据源 */
private Map<Object, Object> backupTargetDataSources;
public Map<Object, Object> getBackupTargetDataSources() {return backupTargetDataSources;} /*defaultDataSource 为默认数据源 */
public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSource) {backupTargetDataSources = targetDataSource; super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(backupTargetDataSources);
super.afterPropertiesSet();} public void addDataSource(String key, DataSource dataSource) {this.backupTargetDataSources.put(key, dataSource);
super.setTargetDataSources(this.backupTargetDataSources);
super.afterPropertiesSet();} /* 返回以后线程的数据源的 key*/
@Override
protected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getContextKey();
}}
定义数据源 key 线程变量持有
定义一个 ThreadLocal 动态变量,该变量持有了线程和线程的数据源 key 之间的关系。当咱们要切换数据源时,首先要本人生成一个 key,将这个 key 存入 threadLocal 线程变量中;同时还应该从 DynamicDataSource 对象中的 backupTargetDataSources 属性中获取到数据源对象,而后将 key 和数据源对象再 put 到 backupTargetDataSources 中。这样,spring 就能依据 determineCurrentLookupKey 办法返回的 key,从 backupTargetDataSources 中取出咱们刚刚设置的数据源对象,进行 getConnection 等一系列操作了。
public class DynamicDataSourceContextHolder {
/**
* 存储线程和数据源 key 的映射关系
*/
private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
/***
* 设置以后线程数据源 key
*/
public static void setContextKey(String key) {DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
} /***
* 获取以后线程数据源 key
*/
public static String getContextKey() {String key = DATASOURCE_CONTEXT_KEY_HOLDER.get(); return key == null ? DataSourceConstants.DS_KEY_MASTER : key;
} /***
* 删除以后线程数据源 key
*/
public static void removeContextKey() {DynamicDataSource dynamicDataSource = RequestHandleMethodRegistry.getContext().getBean(DynamicDataSource.class); String currentKey = DATASOURCE_CONTEXT_KEY_HOLDER.get(); if (StringUtils.isNotBlank(currentKey) && !"master".equals(currentKey)) {dynamicDataSource.getBackupTargetDataSources().remove(currentKey); } DATASOURCE_CONTEXT_KEY_HOLDER.remove();}}
多数据源主动配置类
这里通过读取 yml 配置文件中所有数据源的配置,主动为每个数据源创立 datasource 对象并注册至 bean 工厂。同时将这些数据源对象,设置到 AbstractRoutingDataSource 中。
通过这种形式,前面如果须要增加或批改数据源,都无需新增或批改 java 配置类,只需去配置核心批改 yml 文件即可。
@Configuration
@MapperScan(basePackages = "com.hosjoy.xxx.xxx.modules.xxx.mapper")
public class DynamicDataSourceConfig {
@Autowired
private BeanFactory beanFactory;
@Autowired
private DynamicDataSourceProperty dynamicDataSourceProperty;
/**
* 性能形容: <br>
* 〈动静数据源 bean 主动配置注册所有数据源〉
*
* @param
* @return javax.sql.DataSource
* @Author li.he
* @Date 2020/6/4 16:47
* @Modifier
*/
@Bean
@Primary
public DataSource dynamicDataSource() {DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) beanFactory; /* 获取 yml 所有数据源配置 */
Map<String, Object> datasource = dynamicDataSourceProperty.getDatasource(); Map<Object, Object> dataSourceMap = new HashMap<>(5);
Optional.ofNullable(datasource).ifPresent(map -> { for (Map.Entry<String, Object> entry : map.entrySet()) {
// 创立数据源对象
HikariDataSource dataSource = (HikariDataSource) DataSourceBuilder.create().build();
String dataSourceId = entry.getKey();
configeDataSource(entry, dataSource);
/*bean 工厂注册每个数据源 bean*/
listableBeanFactory.registerSingleton(dataSourceId, dataSource);
dataSourceMap.put(dataSourceId, dataSource);
}
});
//AbstractRoutingDataSource 设置主从数据源
return new DynamicDataSource(beanFactory.getBean("master", DataSource.class), dataSourceMap);
}
private void configeDataSource(Map.Entry<String, Object> entry, HikariDataSource dataSource) {Map<String, Object> dataSourceConfig = (Map<String, Object>) entry.getValue();
dataSource.setJdbcUrl(MapUtils.getString(dataSourceConfig, "jdbcUrl"));
dataSource.setDriverClassName(MapUtils.getString(dataSourceConfig, "driverClassName"));
dataSource.setUsername(MapUtils.getString(dataSourceConfig, "username"));
dataSource.setPassword(MapUtils.getString(dataSourceConfig, "password"));
}
}
数据源切换工具类
切换逻辑:
(1)生成以后线程数据源 key
(2)依据业务条件,获取应切换的数据源 ID;
(3)依据 ID 从数据源缓存池中获取数据源对象,并再次增加到 backupTargetDataSources 缓存池中;
(4)threadLocal 设置以后线程对应的数据源 key;
(5)在执行数据库操作前,spring 会调用 determineCurrentLookupKey 办法获取 key,而后依据 key 去数据源缓存池取出数据源,而后 getConnection 获取该数据源连贯;
(6)应用该数据源执行数据库操作;
(7)开释缓存:threadLocal 清理以后线程数据源信息、数据源缓存池清理以后线程数据源 key 和数据源对象,目标是避免内存透露。
@Slf4j
@Component
public class DataSourceUtil {
@Autowired
private DataSourceConfiger dataSourceConfiger; /* 依据业务条件切换数据源 */
public void switchDataSource(String key, Predicate<? super Map<String, Object>> predicate) {
try {
// 生成以后线程数据源 key
String newDsKey = System.currentTimeMillis() + "";
List<Map<String, Object>> configValues = dataSourceConfiger.getConfigValues(key);
Map<String, Object> db = configValues.stream().filter(predicate)
.findFirst().get();
String id = MapUtils.getString(db, "id");
// 依据 ID 从数据源缓存池中获取数据源对象,并再次增加到 backupTargetDataSources
addDataSource(newDsKey, id);
// 设置以后线程对应的数据源 key
DynamicDataSourceContextHolder.setContextKey(newDsKey);
log.info("以后线程数据源切换胜利,以后数据源 ID:{}", id);
}
catch (Exception e) {log.error("切换数据源失败,请查看数据源配置文件。key:{}, 条件:{}", key, predicate.toString());
throw new ClientException("切换数据源失败,请查看数据源配置", e);
}
}
/* 将数据源增加至少数据源缓存池中 */
public static void addDataSource(String key, String dataSourceId) {DynamicDataSource dynamicDataSource = RequestHandleMethodRegistry.getContext().getBean(DynamicDataSource.class);
DataSource dataSource = (DataSource) dynamicDataSource.getBackupTargetDataSources().get(dataSourceId);
dynamicDataSource.addDataSource(key, dataSource);
}
}
应用
public void doExecute(ReqTestParams reqTestParams){
// 结构条件
Predicate<? super Map<String, Object>> predicate =.........;
// 切换数据源
dataSourceUtil.switchDataSource("testKey", predicate);
// 数据库操作
mapper.testQuery(); // 清理缓存,防止内存透露
DynamicDataSourceContextHolder.removeContextKey();}
每次数据源应用后,都要调用 removeContextKey 办法清理缓存,防止内存透露,这里能够思考用 AOP 拦挡特定办法,利用后置告诉为执行办法代理执行缓存清理工作。
@Aspect
@Component
@Slf4j
public class RequestHandleMethodAspect {@After("xxxxxxxxxxxxxxExecution 表达式 xxxxxxxxxxxxxxxxxx")
public void afterRunning(JoinPoint joinPoint){String name = joinPoint.getSignature().toString(); long id = Thread.currentThread().getId();
log.info("办法执行结束,开始清空以后线程数据源,线程 id:{}, 代理办法:{}",id,name);
DynamicDataSourceContextHolder.removeContextKey(); log.info("以后线程数据源清空结束, 已返回至默认数据源:{}",id);
}}
特点剖析
(1)参数化切换数据源形式,出发点和分包形式不一样,适宜于在运行时能力确定用哪个数据源。
(2)须要手动执行切换数据源操作;
(3)无需分包,mapper 和 xml 门路自在定义;
(4)减少数据源,无需批改 java 配置类,只需批改数据源配置文件即可。
注解形式
思维
该形式利用注解 +AOP 思维,为须要切换数据源的办法标记自定义注解,注解属性指定数据源 ID,而后利用 AOP 切面拦挡注解标记的办法,在办法执行前,切换至相应数据源;在办法执行完结后,切换至默认数据源。
须要留神的是,自定义切面的优先级须要高于 @Transactional 注解对应切面的优先级。
否则,在自定义注解和 @Transactional 同时应用时,@Transactional 切面会优先执行,切面在调用 getConnection 办法时,会去调用 AbstractRoutingDataSource.determineCurrentLookupKey 办法,此时获取到的是默认数据源 master。这时 @UsingDataSource 对应的切面即便再设置以后线程的数据源 key,前面也不会再去调用 determineCurrentLookupKey 办法来切换数据源了。
设计思路
数据源注册
同上。
数据源切换
利用切面,拦挡所有 @UsingDataSource 注解标记的办法,依据 dataSourceId 属性,在办法执行前,切换至相应数据源;在办法执行完结后,清理缓存并切换至默认数据源。
代码实现
数据源配置文件
同上。
定义动静数据源
同上。
定义数据源 key 线程变量持有
同上。
多数据源主动配置类
同上。
数据源切换工具类
切换逻辑:
(1)生成以后线程数据源 key
(3)依据 ID 从数据源缓存池中获取数据源对象,并再次增加到 backupTargetDataSources 缓存池中;
(4)threadLocal 设置以后线程对应的数据源 key;
(5)在执行数据库操作前,spring 会调用 determineCurrentLookupKey 办法获取 key,而后依据 key 去数据源缓存池取出数据源,而后 getConnection 获取该数据源连贯;
(6)应用该数据源执行数据库操作;
(7)开释缓存:threadLocal 清理以后线程数据源信息、数据源缓存池清理以后线程数据源 key 和数据源对象。
public static void switchDataSource(String dataSourceId) {if (StringUtils.isBlank(dataSourceId)) {throw new ClientException("切换数据源失败,数据源 ID 不能为空");
} try {String threadDataSourceKey = UUID.randomUUID().toString(); DataSourceUtil.addDataSource(threadDataSourceKey, dataSourceId); DynamicDataSourceContextHolder.setContextKey(threadDataSourceKey); } catch (Exception e) {log.error("切换数据源失败,未找到指定的数据源,请确保所指定的数据源 ID 已在配置文件中配置。dataSourceId:{}", dataSourceId);
throw new ClientException("切换数据源失败,未找到指定的数据源,请确保所指定的数据源 ID 已在配置文件中配置。dataSourceId:" + dataSourceId, e);
}}
自定义注解
自定义注解标记以后办法所应用的数据源,默认为主数据源。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UsingDataSource {String dataSourceId() default "master";
}
切面
次要是定义前置告诉和后置告诉,拦挡 UsingDataSource 注解标记的办法,办法执行前切换数据源,办法执行后清理数据源缓存。
须要标记切面的优先级比 @Transaction 注解对应切面的优先级要高。否则,在自定义注解和 @Transactional 同时应用时,@Transactional 切面会优先执行,切面在调用 getConnection 办法时,会去调用 AbstractRoutingDataSource.determineCurrentLookupKey 办法,此时获取到的是默认数据源 master。这时 @UsingDataSource 对应的切面即便再设置以后线程的数据源 key,前面也不会再去调用 determineCurrentLookupKey 办法来切换数据源了。
@Aspect
@Component
@Slf4j
@Order(value = 1)
public class DynamicDataSourceAspect { // 拦挡 UsingDataSource 注解标记的办法,办法执行前切换数据源
@Before(value = "@annotation(usingDataSource)")
public void before(JoinPoint joinPoint, UsingDataSource usingDataSource) {String dataSourceId = usingDataSource.dataSourceId();
log.info("执行指标办法前开始切换数据源,指标办法:{}, dataSourceId:{}", joinPoint.getSignature().toString(), dataSourceId);
try {DataSourceUtil.switchDataSource(dataSourceId);
}
catch (Exception e) {log.error("切换数据源失败!数据源可能未配置或不可用, 数据源 ID:{}", dataSourceId, e);
throw new ClientException("切换数据源失败!数据源可能未配置或不可用, 数据源 ID:" + dataSourceId, e);
}
log.info("指标办法:{} , 已切换至数据源:{}", joinPoint.getSignature().toString(), dataSourceId);
}
// 拦挡 UsingDataSource 注解标记的办法,办法执行后清理数据源,避免内存透露
@After(value = "@annotation(com.hosjoy.hbp.dts.common.annotation.UsingDataSource)")
public void after(JoinPoint joinPoint) {log.info("指标办法执行结束,执行清理,切换至默认数据源,指标办法:{}", joinPoint.getSignature().toString());
try {DynamicDataSourceContextHolder.removeContextKey();
}
catch (Exception e) {log.error("清理数据源失败", e);
throw new ClientException("清理数据源失败", e);
}
log.info("指标办法:{} , 数据源清理结束,已返回默认数据源", joinPoint.getSignature().toString());
}
}
应用
@UsingDataSource(dataSourceId = "slave1")
@Transactional
public void test(){AddressPo po = new AddressPo();
po.setMemberCode("asldgjlk");
po.setName("lihe");
po.setPhone("13544986666");
po.setProvince("asdgjwlkgj");
addressMapper.insert(po); int i = 1 / 0;
}
动静增加形式(十分用)
业务场景形容
这种业务场景不是很常见,但必定是有人遇到过的。
我的项目外面只配置了 1 个默认的数据源,而具体运行时须要动静的增加新的数据源,非已配置好的动态的多数据源。例如须要去服务器实时读取数据源配置信息(非配置在本地),而后再执行数据库操作。
这种业务场景,以上 3 种形式就都不实用了,因为上述的数据源都是提前在 yml 文件配置好的。
实现思路
除了第 6 步外,利用之前写好的代码就能够实现。
思路是:
(1)创立新数据源;
(2)DynamicDataSource 注册新数据源;
(3)切换:设置以后线程数据源 key;增加长期数据源;
(4)数据库操作(必须在另一个 service 实现,否则无法控制事务);
(5)清理以后线程数据源 key、清理长期数据源;
(6)清理刚刚注册的数据源;
(7)此时已返回至默认数据源。
代码
代码写的比拟简陋,然而模板大略就是这样子,次要想表白实现的形式。
Service A:
public String testUsingNewDataSource(){DynamicDataSource dynamicDataSource = RequestHandleMethodRegistry.getContext().getBean("dynamicDataSource", DynamicDataSource.class);
try { // 模仿从服务器读取数据源信息
//..........................
//....................
// 创立新数据源
HikariDataSource dataSource = (HikariDataSource) DataSourceBuilder.create().build();
dataSource.setJdbcUrl("jdbc:mysql://192.168.xxx.xxx:xxxx/xxxxx?......");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("xxx");
dataSource.setPassword("xxx");
//DynamicDataSource 注册新数据源
dynamicDataSource.addDataSource("test_ds_id", dataSource);
// 设置以后线程数据源 key、增加长期数据源
DataSourceUtil.switchDataSource("test_ds_id");
// 数据库操作(必须在另一个 service 实现,否则无法控制事务)serviceB.testInsert();}
finally {
// 清理以后线程数据源 key
DynamicDataSourceContextHolder.removeContextKey();
// 清理刚刚注册的数据源
dynamicDataSource.removeDataSource("test_ds_id");
}
return "aa";
}
Service B:
@Transactional(rollbackFor = Exception.class)
public void testInsert() {AddressPo po = new AddressPo();
po.setMemberCode("555555555");
po.setName("李郃");
po.setPhone("16651694996");
po.setProvince("江苏省");
po.setCity("南京市");
po.setArea("浦口区");
po.setAddress("南京市浦口区宁六路 219 号");
po.setDef(false);
po.setCreateBy("23958");
addressMapper.insert(po); // 测试事务回滚
int i = 1 / 0;
}
DynamicDataSource: 减少 removeDataSource 办法,清理注册的新数据源。
public class DynamicDataSource extends AbstractRoutingDataSource {................. ................. ................. public void removeDataSource(String key){this.backupTargetDataSources.remove(key);
super.setTargetDataSources(this.backupTargetDataSources);
super.afterPropertiesSet();} ................. ................. .................}
四种形式比照
事务问题
应用上述数据源配置形式,可实现单个数据源事务管制。
例如在一个 service 办法中,须要操作多个数据源执行 CUD 时,是能够实现单个数据源事务管制的。形式如下,别离将须要事务管制的办法独自抽取到另一个 service,可实现单个事务办法的事务管制。
ServiceA:
public void updateMuilty(){serviceB.updateDb1(); serviceB.updateDb2();}
ServiceB:
@UsingDataSource(dataSourceId = "slave1")
@Transactional
public void updateDb1(){// 业务逻辑......}
@UsingDataSource(dataSourceId = "slave2")
@Transactional
public void updateDb2(){// 业务逻辑......}
然而在同一个办法里管制多个数据源的事务就不是这么简略了,这就属于分布式事务的范畴,能够思考应用 atomikos 开源我的项目实现 JTA 分布式事务处理或者阿里的 Fescar 框架。
因为波及到分布式事务管制,实现比较复杂,这里只是引出这个问题,前面抽时间把这块补上来。