Springcloud+Mybatis使用多數據源的四種方式

前段時間在作會員中心和中間件系統開發時,屢次碰到多數據源配置問題,主要用到分包方式、參數化切換、註解+AOP、動態添加 這四種方式。這裏作一下總結,分享下使用心得以及踩過的坑。java

分包方式

數據源配置文件

在yml中,配置兩個數據源,id分別爲master和s1。mysql

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數據源配置類

注意點:spring

須要用@Primary註解指定默認數據源,不然spring不知道哪一個是主數據源;sql

@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包等,沒有實現 「熱插拔」 效果。session

參數化切換方式

思想

參數化切換數據源,意思是,業務側須要根據當前業務參數,動態的切換到不一樣的數據源。mybatis

這與分包思想不一樣。分包的前提是在編寫代碼的時候,就已經知道當前須要用哪一個數據源,而參數化切換數據源須要根據業務參數決定用哪一個數據源。app

例如,請求參數userType值爲1時,須要切換到數據源slave1;請求參數userType值爲2時,須要切換到數據源slave2。

/**僞代碼**/
int userType = reqUser.getType();
if (userType == 1){
 //切換到數據源slave1
 //數據庫操做
} else if(userType == 2){
 //切換到數據源slave2
 //數據庫操做
}

設計思路

數據源註冊

數據源配置類建立datasource時,從yml配置文件中讀取全部數據源配置,自動建立每一個數據源,並註冊至bean工廠和AbstractRoutingDatasource(後面聊聊這個),同時返回默認的數據源master。

Springcloud+Mybatis使用多數據源的四種方式

數據源切換

(1)經過線程池處理請求,每一個請求獨佔一個線程,這樣每一個線程切換數據源時互不影響。

(2)根據業務參數獲取應切換的數據源ID,根據ID從數據源緩存池獲取數據源bean;

(3)生成當前線程數據源key;

(4)將key設置到threadLocal;

(5)將key和數據源bean放入數據源緩存池;

(6)在執行mapper方法前,spring會調用determineCurrentLookupKey方法獲取key,而後根據key去數據源緩存池取出數據源,而後getConnection獲取該數據源鏈接;

(7)使用該數據源執行數據庫操做;

(8)釋放當前線程數據源。

Springcloud+Mybatis使用多數據源的四種方式

AbstractRoutingDataSource源碼分析

spring爲咱們提供了AbstractRoutingDataSource抽象類,該類就是實現動態切換數據源的關鍵。

咱們看下該類的類圖,其實現了DataSource接口。

Springcloud+Mybatis使用多數據源的四種方式

咱們看下它的getConnection方法的邏輯,其首先調用determineTargetDataSource來獲取數據源,再獲取數據庫鏈接。很容易猜測到就是這裏來決定具體使用哪一個數據源的。

Springcloud+Mybatis使用多數據源的四種方式

進入到determineTargetDataSource方法,咱們能夠看到它先是調用determineCurrentLookupKey獲取到一個lookupKey,而後根據這個key去resolvedDataSources裏去找相應的數據源。

Springcloud+Mybatis使用多數據源的四種方式

看下該類定義的幾個對象,defaultTargetDataSource是默認數據源,resolvedDataSources是一個map對象,存儲全部主從數據源。

Springcloud+Mybatis使用多數據源的四種方式

因此,關鍵就是這個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屬性,在方法執行前,切換至相應數據源;在方法執行結束後,清理緩存並切換至默認數據源。

Springcloud+Mybatis使用多數據源的四種方式

代碼實現

數據源配置文件

同上。

定義動態數據源

同上。

定義數據源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();
    }                .................            .................            .................}

四種方式對比

Springcloud+Mybatis使用多數據源的四種方式

事務問題

使用上述數據源配置方式,可實現單個數據源事務控制。

例如在一個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框架。

因爲涉及到分佈式事務控制,實現比較複雜,這裏只是引出這個問題,後面抽時間把這塊補上來。

image.png

相關文章
相關標籤/搜索