2021-05-12 14:32:11
Springcloud+Mybatis使用多資料來源的四種方式(小結)
前段時間在做會員中心和中介軟體系統開發時,多次碰到多資料來源設定問題,主要用到分包方式、引數化切換、註解+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(); } ................. ................. ................. }
四種方式對比
分包方式 | 引數化切換 | 註解方式 | 動態新增方式 | |
---|---|---|---|---|
適用場景 | 編碼時便知道用哪個資料來源 | 執行時才能確定用哪個資料來源 | 編碼時便知道用哪個資料來源 | 執行時動態新增新資料來源 |
切換模式 | 自動 | 手動 | 自動 | 手動 |
mapper路徑 | 需要分包 | 無要求 | 無要求 | 無要求 |
增加資料來源是否需要修改設定類 | 需要 | 不需要 | 不需要 | |
實現複雜度 | 簡單 | 複雜 | 複雜 | 複雜 |
事務問題
使用上述資料來源設定方式,可實現單個資料來源事務控制。
例如在一個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框架。
由於涉及到分散式事務控制,實現比較複雜,這裡只是引出這個問題,後面抽時間把這塊補上來。
到此這篇關於Springcloud+Mybatis使用多資料來源的四種方式(小結)的文章就介紹到這了,更多相關Springcloud Mybatis多資料來源內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章