国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|編程技術(shù)|正則表達(dá)式|

服務(wù)器之家 - 編程語(yǔ)言 - JAVA教程 - Springcloud+Mybatis使用多數(shù)據(jù)源的四種方式(小結(jié))

Springcloud+Mybatis使用多數(shù)據(jù)源的四種方式(小結(jié))

2020-09-24 00:31java架構(gòu)師小芷 JAVA教程

這篇文章主要介紹了Springcloud+Mybatis使用多數(shù)據(jù)源的四種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前段時(shí)間在做會(huì)員中心和中間件系統(tǒng)開發(fā)時(shí),多次碰到多數(shù)據(jù)源配置問題,主要用到分包方式、參數(shù)化切換、注解+AOP、動(dòng)態(tài)添加 這四種方式。這里做一下總結(jié),分享下使用心得以及踩過的坑。

分包方式

 

數(shù)據(jù)源配置文件

在yml中,配置兩個(gè)數(shù)據(jù)源,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

數(shù)據(jù)源配置類

 master數(shù)據(jù)源配置類

注意點(diǎn):

需要用@Primary注解指定默認(rèn)數(shù)據(jù)源,否則spring不知道哪個(gè)是主數(shù)據(jù)源;

@Configuration
@MapperScan(basePackages = "com.hosjoy.xxx.xxx.xxx.xxx.mapper.master", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataSourceConfig {

  //默認(rèn)數(shù)據(jù)源
  @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(
        // 設(shè)置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數(shù)據(jù)源配置類

@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(
        // 設(shè)置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文件,需要按照不同的數(shù)據(jù)源分包。在操作數(shù)據(jù)庫(kù)時(shí),根據(jù)需要在service類中注入dao層。

特點(diǎn)分析

優(yōu)點(diǎn)

實(shí)現(xiàn)起來簡(jiǎn)單,只需要編寫數(shù)據(jù)源配置文件和配置類,mapper接口和xml文件注意分包即可。

缺點(diǎn)

很明顯,如果后面要增加或刪除數(shù)據(jù)源,不僅要修改數(shù)據(jù)源配置文件,還需要修改配置類。

例如增加一個(gè)數(shù)據(jù)源,同時(shí)還需要新寫一個(gè)該數(shù)據(jù)源的配置類,同時(shí)還要考慮新建mapper接口包、xml包等,沒有實(shí)現(xiàn) “熱插拔” 效果。

參數(shù)化切換方式

 

 思想

參數(shù)化切換數(shù)據(jù)源,意思是,業(yè)務(wù)側(cè)需要根據(jù)當(dāng)前業(yè)務(wù)參數(shù),動(dòng)態(tài)的切換到不同的數(shù)據(jù)源。

這與分包思想不同。分包的前提是在編寫代碼的時(shí)候,就已經(jīng)知道當(dāng)前需要用哪個(gè)數(shù)據(jù)源,而參數(shù)化切換數(shù)據(jù)源需要根據(jù)業(yè)務(wù)參數(shù)決定用哪個(gè)數(shù)據(jù)源。

例如,請(qǐng)求參數(shù)userType值為1時(shí),需要切換到數(shù)據(jù)源slave1;請(qǐng)求參數(shù)userType值為2時(shí),需要切換到數(shù)據(jù)源slave2。

/**偽代碼**/
int userType = reqUser.getType();
if (userType == 1){
  //切換到數(shù)據(jù)源slave1
  //數(shù)據(jù)庫(kù)操作
} else if(userType == 2){
  //切換到數(shù)據(jù)源slave2
  //數(shù)據(jù)庫(kù)操作
}

設(shè)計(jì)思路

 數(shù)據(jù)源注冊(cè)

數(shù)據(jù)源配置類創(chuàng)建datasource時(shí),從yml配置文件中讀取所有數(shù)據(jù)源配置,自動(dòng)創(chuàng)建每個(gè)數(shù)據(jù)源,并注冊(cè)至bean工廠和AbstractRoutingDatasource(后面聊聊這個(gè)),同時(shí)返回默認(rèn)的數(shù)據(jù)源master。

Springcloud+Mybatis使用多數(shù)據(jù)源的四種方式(小結(jié))

 數(shù)據(jù)源切換

(1)通過線程池處理請(qǐng)求,每個(gè)請(qǐng)求獨(dú)占一個(gè)線程,這樣每個(gè)線程切換數(shù)據(jù)源時(shí)互不影響。

(2)根據(jù)業(yè)務(wù)參數(shù)獲取應(yīng)切換的數(shù)據(jù)源ID,根據(jù)ID從數(shù)據(jù)源緩存池獲取數(shù)據(jù)源bean;

(3)生成當(dāng)前線程數(shù)據(jù)源key;

(4)將key設(shè)置到threadLocal;

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

(6)在執(zhí)行mapper方法前,spring會(huì)調(diào)用determineCurrentLookupKey方法獲取key,然后根據(jù)key去數(shù)據(jù)源緩存池取出數(shù)據(jù)源,然后getConnection獲取該數(shù)據(jù)源連接;

(7)使用該數(shù)據(jù)源執(zhí)行數(shù)據(jù)庫(kù)操作;

(8)釋放當(dāng)前線程數(shù)據(jù)源。

Springcloud+Mybatis使用多數(shù)據(jù)源的四種方式(小結(jié))

AbstractRoutingDataSource源碼分析

spring為我們提供了AbstractRoutingDataSource抽象類,該類就是實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源的關(guān)鍵。

我們看下該類的類圖,其實(shí)現(xiàn)了DataSource接口。

Springcloud+Mybatis使用多數(shù)據(jù)源的四種方式(小結(jié))

我們看下它的getConnection方法的邏輯,其首先調(diào)用determineTargetDataSource來獲取數(shù)據(jù)源,再獲取數(shù)據(jù)庫(kù)連接。很容易猜想到就是這里來決定具體使用哪個(gè)數(shù)據(jù)源的。

Springcloud+Mybatis使用多數(shù)據(jù)源的四種方式(小結(jié))

進(jìn)入到determineTargetDataSource方法,我們可以看到它先是調(diào)用determineCurrentLookupKey獲取到一個(gè)lookupKey,然后根據(jù)這個(gè)key去resolvedDataSources里去找相應(yīng)的數(shù)據(jù)源。

Springcloud+Mybatis使用多數(shù)據(jù)源的四種方式(小結(jié))

看下該類定義的幾個(gè)對(duì)象,defaultTargetDataSource是默認(rèn)數(shù)據(jù)源,resolvedDataSources是一個(gè)map對(duì)象,存儲(chǔ)所有主從數(shù)據(jù)源。

Springcloud+Mybatis使用多數(shù)據(jù)源的四種方式(小結(jié))

所以,關(guān)鍵就是這個(gè)lookupKey的獲取邏輯,決定了當(dāng)前獲取的是哪個(gè)數(shù)據(jù)源,然后執(zhí)行g(shù)etConnection等一系列操作。determineCurrentLookupKey是AbstractRoutingDataSource類中的一個(gè)抽象方法,而它的返回值是你所要用的數(shù)據(jù)源dataSource的key值,有了這個(gè)key值,resolvedDataSource(這是個(gè)map,由配置文件中設(shè)置好后存入的)就從中取出對(duì)應(yīng)的DataSource,如果找不到,就用配置默認(rèn)的數(shù)據(jù)源。

所以,通過擴(kuò)展AbstractRoutingDataSource類,并重寫其中的determineCurrentLookupKey()方法,可以實(shí)現(xiàn)數(shù)據(jù)源的切換。

代碼實(shí)現(xiàn)

下面貼出關(guān)鍵代碼實(shí)現(xiàn)。

數(shù)據(jù)源配置文件

這里配了3個(gè)數(shù)據(jù)源,其中主數(shù)據(jù)源是MySQL,兩個(gè)從數(shù)據(jù)源是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

定義動(dòng)態(tài)數(shù)據(jù)源

主要是繼承AbstractRoutingDataSource,實(shí)現(xiàn)determineCurrentLookupKey方法。

public class DynamicDataSource extends AbstractRoutingDataSource {
  /*存儲(chǔ)所有數(shù)據(jù)源*/
  private Map<Object, Object> backupTargetDataSources;

  public Map<Object, Object> getBackupTargetDataSources() {
    return backupTargetDataSources;
  }
  /*defaultDataSource為默認(rèn)數(shù)據(jù)源*/
  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();
  }
  /*返回當(dāng)前線程的數(shù)據(jù)源的key*/
  @Override
  protected Object determineCurrentLookupKey() {
    return DynamicDataSourceContextHolder.getContextKey();
  }
}

定義數(shù)據(jù)源key線程變量持有

定義一個(gè)ThreadLocal靜態(tài)變量,該變量持有了線程和線程的數(shù)據(jù)源key之間的關(guān)系。當(dāng)我們要切換數(shù)據(jù)源時(shí),首先要自己生成一個(gè)key,將這個(gè)key存入threadLocal線程變量中;同時(shí)還應(yīng)該從DynamicDataSource對(duì)象中的backupTargetDataSources屬性中獲取到數(shù)據(jù)源對(duì)象, 然后將key和數(shù)據(jù)源對(duì)象再put到backupTargetDataSources中。 這樣,spring就能根據(jù)determineCurrentLookupKey方法返回的key,從backupTargetDataSources中取出我們剛剛設(shè)置的數(shù)據(jù)源對(duì)象,進(jìn)行g(shù)etConnection等一系列操作了。

public class DynamicDataSourceContextHolder {
  /**
   * 存儲(chǔ)線程和數(shù)據(jù)源key的映射關(guān)系
   */
  private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();

  /***
   * 設(shè)置當(dāng)前線程數(shù)據(jù)源key
   */
  public static void setContextKey(String key) {
    DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
  }
  /***
   * 獲取當(dāng)前線程數(shù)據(jù)源key
   */
  public static String getContextKey() {
    String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
    return key == null ? DataSourceConstants.DS_KEY_MASTER : key;
  }
  /***
   * 刪除當(dāng)前線程數(shù)據(jù)源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();
  }
}

多數(shù)據(jù)源自動(dòng)配置類

這里通過讀取yml配置文件中所有數(shù)據(jù)源的配置,自動(dòng)為每個(gè)數(shù)據(jù)源創(chuàng)建datasource 對(duì)象并注冊(cè)至bean工廠。同時(shí)將這些數(shù)據(jù)源對(duì)象,設(shè)置到AbstractRoutingDataSource中。

通過這種方式,后面如果需要添加或修改數(shù)據(jù)源,都無需新增或修改java配置類,只需去配置中心修改yml文件即可。

@Configuration
@MapperScan(basePackages = "com.hosjoy.xxx.xxx.modules.xxx.mapper")
public class DynamicDataSourceConfig {
  @Autowired
  private BeanFactory beanFactory;
  @Autowired
  private DynamicDataSourceProperty dynamicDataSourceProperty;
  /**
   * 功能描述: <br>
   * 〈動(dòng)態(tài)數(shù)據(jù)源bean 自動(dòng)配置注冊(cè)所有數(shù)據(jù)源〉
   *
   * @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所有數(shù)據(jù)源配置*/
    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()) {
        //創(chuàng)建數(shù)據(jù)源對(duì)象
        HikariDataSource dataSource = (HikariDataSource) DataSourceBuilder.create().build();
        String dataSourceId = entry.getKey();
        configeDataSource(entry, dataSource);
        /*bean工廠注冊(cè)每個(gè)數(shù)據(jù)源bean*/
        listableBeanFactory.registerSingleton(dataSourceId, dataSource);
        dataSourceMap.put(dataSourceId, dataSource);
      }
    });
    //AbstractRoutingDataSource設(shè)置主從數(shù)據(jù)源
    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"));
  }

}

數(shù)據(jù)源切換工具類

切換邏輯:

(1)生成當(dāng)前線程數(shù)據(jù)源key

(2)根據(jù)業(yè)務(wù)條件,獲取應(yīng)切換的數(shù)據(jù)源ID;

(3)根據(jù)ID從數(shù)據(jù)源緩存池中獲取數(shù)據(jù)源對(duì)象,并再次添加到backupTargetDataSources緩存池中;

(4)threadLocal設(shè)置當(dāng)前線程對(duì)應(yīng)的數(shù)據(jù)源key;

(5)在執(zhí)行數(shù)據(jù)庫(kù)操作前,spring會(huì)調(diào)用determineCurrentLookupKey方法獲取key,然后根據(jù)key去數(shù)據(jù)源緩存池取出數(shù)據(jù)源,然后getConnection獲取該數(shù)據(jù)源連接;

(6)使用該數(shù)據(jù)源執(zhí)行數(shù)據(jù)庫(kù)操作;

(7)釋放緩存:threadLocal清理當(dāng)前線程數(shù)據(jù)源信息、數(shù)據(jù)源緩存池清理當(dāng)前線程數(shù)據(jù)源key和數(shù)據(jù)源對(duì)象,目的是防止內(nèi)存泄漏。

@Slf4j
@Component
public class DataSourceUtil {
  @Autowired
  private DataSourceConfiger dataSourceConfiger;
  
  /*根據(jù)業(yè)務(wù)條件切換數(shù)據(jù)源*/
  public void switchDataSource(String key, Predicate<? super Map<String, Object>> predicate) {
    try {
      //生成當(dāng)前線程數(shù)據(jù)源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");
      //根據(jù)ID從數(shù)據(jù)源緩存池中獲取數(shù)據(jù)源對(duì)象,并再次添加到backupTargetDataSources
      addDataSource(newDsKey, id);
      //設(shè)置當(dāng)前線程對(duì)應(yīng)的數(shù)據(jù)源key
      DynamicDataSourceContextHolder.setContextKey(newDsKey);
      log.info("當(dāng)前線程數(shù)據(jù)源切換成功,當(dāng)前數(shù)據(jù)源ID:{}", id);

    }
    catch (Exception e) {
      log.error("切換數(shù)據(jù)源失敗,請(qǐng)檢查數(shù)據(jù)源配置文件。key:{}, 條件:{}", key, predicate.toString());
      throw new ClientException("切換數(shù)據(jù)源失敗,請(qǐng)檢查數(shù)據(jù)源配置", e);
    }
  }
  
  /*將數(shù)據(jù)源添加至多數(shù)據(jù)源緩存池中*/
  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){
  //構(gòu)造條件
  Predicate<? super Map<String, Object>> predicate =.........;
  //切換數(shù)據(jù)源
  dataSourceUtil.switchDataSource("testKey", predicate);
  //數(shù)據(jù)庫(kù)操作
  mapper.testQuery();
  //清理緩存,避免內(nèi)存泄漏
  DynamicDataSourceContextHolder.removeContextKey();
}

每次數(shù)據(jù)源使用后,都要調(diào)用removeContextKey方法清理緩存,避免內(nèi)存泄漏,這里可以考慮用AOP攔截特定方法,利用后置通知為執(zhí)行方法代理執(zhí)行緩存清理工作。

@Aspect
@Component
@Slf4j
public class RequestHandleMethodAspect {
  @After("xxxxxxxxxxxxxxExecution表達(dá)式xxxxxxxxxxxxxxxxxx")
  public void afterRunning(JoinPoint joinPoint){
    String name = joinPoint.getSignature().toString();
    long id = Thread.currentThread().getId();
    log.info("方法執(zhí)行完畢,開始清空當(dāng)前線程數(shù)據(jù)源,線程id:{},代理方法:{}",id,name);
    DynamicDataSourceContextHolder.removeContextKey();
    log.info("當(dāng)前線程數(shù)據(jù)源清空完畢,已返回至默認(rèn)數(shù)據(jù)源:{}",id);
  }
}

特點(diǎn)分析

(1)參數(shù)化切換數(shù)據(jù)源方式,出發(fā)點(diǎn)和分包方式不一樣,適合于在運(yùn)行時(shí)才能確定用哪個(gè)數(shù)據(jù)源。

(2)需要手動(dòng)執(zhí)行切換數(shù)據(jù)源操作;

(3)無需分包,mapper和xml路徑自由定義;

(4)增加數(shù)據(jù)源,無需修改java配置類,只需修改數(shù)據(jù)源配置文件即可。

注解方式

 

思想

該方式利用注解+AOP思想,為需要切換數(shù)據(jù)源的方法標(biāo)記自定義注解,注解屬性指定數(shù)據(jù)源ID,然后利用AOP切面攔截注解標(biāo)記的方法,在方法執(zhí)行前,切換至相應(yīng)數(shù)據(jù)源;在方法執(zhí)行結(jié)束后,切換至默認(rèn)數(shù)據(jù)源。

需要注意的是,自定義切面的優(yōu)先級(jí)需要高于@Transactional注解對(duì)應(yīng)切面的優(yōu)先級(jí)。

否則,在自定義注解和@Transactional同時(shí)使用時(shí),@Transactional切面會(huì)優(yōu)先執(zhí)行,切面在調(diào)用getConnection方法時(shí),會(huì)去調(diào)用AbstractRoutingDataSource.determineCurrentLookupKey方法,此時(shí)獲取到的是默認(rèn)數(shù)據(jù)源master。這時(shí)@UsingDataSource對(duì)應(yīng)的切面即使再設(shè)置當(dāng)前線程的數(shù)據(jù)源key,后面也不會(huì)再去調(diào)用determineCurrentLookupKey方法來切換數(shù)據(jù)源了。

設(shè)計(jì)思路

數(shù)據(jù)源注冊(cè)

同上。

數(shù)據(jù)源切換

利用切面,攔截所有@UsingDataSource注解標(biāo)記的方法,根據(jù)dataSourceId屬性,在方法執(zhí)行前,切換至相應(yīng)數(shù)據(jù)源;在方法執(zhí)行結(jié)束后,清理緩存并切換至默認(rèn)數(shù)據(jù)源。

Springcloud+Mybatis使用多數(shù)據(jù)源的四種方式(小結(jié))

代碼實(shí)現(xiàn)

數(shù)據(jù)源配置文件

同上。

定義動(dòng)態(tài)數(shù)據(jù)源

同上。

定義數(shù)據(jù)源key線程變量持有

同上。

多數(shù)據(jù)源自動(dòng)配置類

同上。

數(shù)據(jù)源切換工具類

切換邏輯:

(1)生成當(dāng)前線程數(shù)據(jù)源key

(3)根據(jù)ID從數(shù)據(jù)源緩存池中獲取數(shù)據(jù)源對(duì)象,并再次添加到backupTargetDataSources緩存池中;

(4)threadLocal設(shè)置當(dāng)前線程對(duì)應(yīng)的數(shù)據(jù)源key;

(5)在執(zhí)行數(shù)據(jù)庫(kù)操作前,spring會(huì)調(diào)用determineCurrentLookupKey方法獲取key,然后根據(jù)key去數(shù)據(jù)源緩存池取出數(shù)據(jù)源,然后getConnection獲取該數(shù)據(jù)源連接;

(6)使用該數(shù)據(jù)源執(zhí)行數(shù)據(jù)庫(kù)操作;

(7)釋放緩存:threadLocal清理當(dāng)前線程數(shù)據(jù)源信息、數(shù)據(jù)源緩存池清理當(dāng)前線程數(shù)據(jù)源key和數(shù)據(jù)源對(duì)象。

public static void switchDataSource(String dataSourceId) {
  if (StringUtils.isBlank(dataSourceId)) {
    throw new ClientException("切換數(shù)據(jù)源失敗,數(shù)據(jù)源ID不能為空");
  }
  try {
    String threadDataSourceKey = UUID.randomUUID().toString();
    DataSourceUtil.addDataSource(threadDataSourceKey, dataSourceId);
    DynamicDataSourceContextHolder.setContextKey(threadDataSourceKey);
  }
  catch (Exception e) {
    log.error("切換數(shù)據(jù)源失敗,未找到指定的數(shù)據(jù)源,請(qǐng)確保所指定的數(shù)據(jù)源ID已在配置文件中配置。dataSourceId:{}", dataSourceId);
    throw new ClientException("切換數(shù)據(jù)源失敗,未找到指定的數(shù)據(jù)源,請(qǐng)確保所指定的數(shù)據(jù)源ID已在配置文件中配置。dataSourceId:" + dataSourceId, e);
  }
}

自定義注解

自定義注解標(biāo)記當(dāng)前方法所使用的數(shù)據(jù)源,默認(rèn)為主數(shù)據(jù)源。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UsingDataSource {

  String dataSourceId() default "master";
}

切面

主要是定義前置通知和后置通知,攔截UsingDataSource注解標(biāo)記的方法,方法執(zhí)行前切換數(shù)據(jù)源,方法執(zhí)行后清理數(shù)據(jù)源緩存。

需要標(biāo)記切面的優(yōu)先級(jí)比@Transaction注解對(duì)應(yīng)切面的優(yōu)先級(jí)要高。否則,在自定義注解和@Transactional同時(shí)使用時(shí),@Transactional切面會(huì)優(yōu)先執(zhí)行,切面在調(diào)用getConnection方法時(shí),會(huì)去調(diào)用AbstractRoutingDataSource.determineCurrentLookupKey方法,此時(shí)獲取到的是默認(rèn)數(shù)據(jù)源master。這時(shí)@UsingDataSource對(duì)應(yīng)的切面即使再設(shè)置當(dāng)前線程的數(shù)據(jù)源key,后面也不會(huì)再去調(diào)用determineCurrentLookupKey方法來切換數(shù)據(jù)源了。

@Aspect
@Component
@Slf4j
@Order(value = 1)
public class DynamicDataSourceAspect {

  //攔截UsingDataSource注解標(biāo)記的方法,方法執(zhí)行前切換數(shù)據(jù)源
  @Before(value = "@annotation(usingDataSource)")
  public void before(JoinPoint joinPoint, UsingDataSource usingDataSource) {
    String dataSourceId = usingDataSource.dataSourceId();
    log.info("執(zhí)行目標(biāo)方法前開始切換數(shù)據(jù)源,目標(biāo)方法:{}, dataSourceId:{}", joinPoint.getSignature().toString(), dataSourceId);
    try {
      DataSourceUtil.switchDataSource(dataSourceId);
    }
    catch (Exception e) {
      log.error("切換數(shù)據(jù)源失??!數(shù)據(jù)源可能未配置或不可用,數(shù)據(jù)源ID:{}", dataSourceId, e);
      throw new ClientException("切換數(shù)據(jù)源失敗!數(shù)據(jù)源可能未配置或不可用,數(shù)據(jù)源ID:" + dataSourceId, e);
    }
    log.info("目標(biāo)方法:{} , 已切換至數(shù)據(jù)源:{}", joinPoint.getSignature().toString(), dataSourceId);
  }

  //攔截UsingDataSource注解標(biāo)記的方法,方法執(zhí)行后清理數(shù)據(jù)源,防止內(nèi)存泄漏
  @After(value = "@annotation(com.hosjoy.hbp.dts.common.annotation.UsingDataSource)")
  public void after(JoinPoint joinPoint) {
    log.info("目標(biāo)方法執(zhí)行完畢,執(zhí)行清理,切換至默認(rèn)數(shù)據(jù)源,目標(biāo)方法:{}", joinPoint.getSignature().toString());
    try {
      DynamicDataSourceContextHolder.removeContextKey();
    }
    catch (Exception e) {
      log.error("清理數(shù)據(jù)源失敗", e);
      throw new ClientException("清理數(shù)據(jù)源失敗", e);
    }
    log.info("目標(biāo)方法:{} , 數(shù)據(jù)源清理完畢,已返回默認(rèn)數(shù)據(jù)源", 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;
}

動(dòng)態(tài)添加方式(非常用)

 

 業(yè)務(wù)場(chǎng)景描述

這種業(yè)務(wù)場(chǎng)景不是很常見,但肯定是有人遇到過的。

項(xiàng)目里面只配置了1個(gè)默認(rèn)的數(shù)據(jù)源,而具體運(yùn)行時(shí)需要?jiǎng)討B(tài)的添加新的數(shù)據(jù)源,非已配置好的靜態(tài)的多數(shù)據(jù)源。例如需要去服務(wù)器實(shí)時(shí)讀取數(shù)據(jù)源配置信息(非配置在本地),然后再執(zhí)行數(shù)據(jù)庫(kù)操作。

這種業(yè)務(wù)場(chǎng)景,以上3種方式就都不適用了,因?yàn)樯鲜龅臄?shù)據(jù)源都是提前在yml文件配制好的。

實(shí)現(xiàn)思路

除了第6步外,利用之前寫好的代碼就可以實(shí)現(xiàn)。

思路是:

(1)創(chuàng)建新數(shù)據(jù)源;

(2)DynamicDataSource注冊(cè)新數(shù)據(jù)源;

(3)切換:設(shè)置當(dāng)前線程數(shù)據(jù)源key;添加臨時(shí)數(shù)據(jù)源;

(4)數(shù)據(jù)庫(kù)操作(必須在另一個(gè)service實(shí)現(xiàn),否則無法控制事務(wù));

(5)清理當(dāng)前線程數(shù)據(jù)源key、清理臨時(shí)數(shù)據(jù)源;

(6)清理剛剛注冊(cè)的數(shù)據(jù)源;

(7)此時(shí)已返回至默認(rèn)數(shù)據(jù)源。

代碼

代碼寫的比較粗陋,但是模板大概就是這樣子,主要想表達(dá)實(shí)現(xiàn)的方式。

Service A:

public String testUsingNewDataSource(){
    DynamicDataSource dynamicDataSource = RequestHandleMethodRegistry.getContext().getBean("dynamicDataSource", DynamicDataSource.class);
    try {
      //模擬從服務(wù)器讀取數(shù)據(jù)源信息
      //..........................
      //....................
      
      //創(chuàng)建新數(shù)據(jù)源
      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注冊(cè)新數(shù)據(jù)源
      dynamicDataSource.addDataSource("test_ds_id", dataSource);

      //設(shè)置當(dāng)前線程數(shù)據(jù)源key、添加臨時(shí)數(shù)據(jù)源
      DataSourceUtil.switchDataSource("test_ds_id");

      //數(shù)據(jù)庫(kù)操作(必須在另一個(gè)service實(shí)現(xiàn),否則無法控制事務(wù))
      serviceB.testInsert();
    }
    finally {
      //清理當(dāng)前線程數(shù)據(jù)源key
      DynamicDataSourceContextHolder.removeContextKey();

      //清理剛剛注冊(cè)的數(shù)據(jù)源
      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("浦口區(qū)");
    po.setAddress("南京市浦口區(qū)寧六路219號(hào)");
    po.setDef(false);
    po.setCreateBy("23958");
    addressMapper.insert(po);
    //測(cè)試事務(wù)回滾
    int i = 1 / 0;
  }

DynamicDataSource: 增加removeDataSource方法, 清理注冊(cè)的新數(shù)據(jù)源。

public class DynamicDataSource extends AbstractRoutingDataSource {
  
      .................
      .................
      .................
  public void removeDataSource(String key){
    this.backupTargetDataSources.remove(key);
    super.setTargetDataSources(this.backupTargetDataSources);
    super.afterPropertiesSet();
  }
  
      .................
      .................
      .................
}

四種方式對(duì)比 

 

 

  分包方式 參數(shù)化切換 注解方式 動(dòng)態(tài)添加方式
適用場(chǎng)景 編碼時(shí)便知道用哪個(gè)數(shù)據(jù)源 運(yùn)行時(shí)才能確定用哪個(gè)數(shù)據(jù)源 編碼時(shí)便知道用哪個(gè)數(shù)據(jù)源 運(yùn)行時(shí)動(dòng)態(tài)添加新數(shù)據(jù)源
切換模式 自動(dòng) 手動(dòng) 自動(dòng) 手動(dòng)
mapper路徑 需要分包 無要求 無要求 無要求
增加數(shù)據(jù)源是否需要修改配置類 需要 不需要 不需要  
實(shí)現(xiàn)復(fù)雜度 簡(jiǎn)單 復(fù)雜 復(fù)雜 復(fù)雜

 

事務(wù)問題

 

使用上述數(shù)據(jù)源配置方式,可實(shí)現(xiàn)單個(gè)數(shù)據(jù)源事務(wù)控制。

例如在一個(gè)service方法中,需要操作多個(gè)數(shù)據(jù)源執(zhí)行CUD時(shí),是可以實(shí)現(xiàn)單個(gè)數(shù)據(jù)源事務(wù)控制的。方式如下,分別將需要事務(wù)控制的方法單獨(dú)抽取到另一個(gè)service,可實(shí)現(xiàn)單個(gè)事務(wù)方法的事務(wù)控制。

ServiceA:

public void updateMuilty(){
   serviceB.updateDb1();
   serviceB.updateDb2();
}

ServiceB:

@UsingDataSource(dataSourceId = "slave1")
@Transactional
public void updateDb1(){
  //業(yè)務(wù)邏輯......
}

@UsingDataSource(dataSourceId = "slave2")
@Transactional
public void updateDb2(){
  //業(yè)務(wù)邏輯......
}

但是在同一個(gè)方法里控制多個(gè)數(shù)據(jù)源的事務(wù)就不是這么簡(jiǎn)單了,這就屬于分布式事務(wù)的范圍,可以考慮使用atomikos開源項(xiàng)目實(shí)現(xiàn)JTA分布式事務(wù)處理或者阿里的Fescar框架。

由于涉及到分布式事務(wù)控制,實(shí)現(xiàn)比較復(fù)雜,這里只是引出這個(gè)問題,后面抽時(shí)間把這塊補(bǔ)上來。

到此這篇關(guān)于Springcloud+Mybatis使用多數(shù)據(jù)源的四種方式(小結(jié))的文章就介紹到這了,更多相關(guān)Springcloud Mybatis多數(shù)據(jù)源內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!

原文鏈接:https://juejin.im/post/6875111962733182990

延伸 · 閱讀

精彩推薦
  • JAVA教程詳解java中的byte類型

    詳解java中的byte類型

    Java也提供了一個(gè)byte數(shù)據(jù)類型,并且是基本類型。java byte是做為最小的數(shù)字來處理的,因此它的值域被定義為-128~127,也就是signed byte。下面這篇文章主要給...

    夜有所思,日有所夢(mèng)4412020-08-18
  • JAVA教程Java中使用json與前臺(tái)Ajax數(shù)據(jù)交互的方法

    Java中使用json與前臺(tái)Ajax數(shù)據(jù)交互的方法

    這篇文章主要為大家詳細(xì)介紹了Java中使用json與前臺(tái)Ajax數(shù)據(jù)交互的方法,分享Ajax獲取顯示Json數(shù)據(jù)的一種方法,感興趣的小伙伴們可以參考一下 ...

    海潛1652020-05-14
  • JAVA教程關(guān)于Java集合框架面試題(含答案)下

    關(guān)于Java集合框架面試題(含答案)下

    Java集合框架為Java編程語(yǔ)言的基礎(chǔ),也是Java面試中很重要的一個(gè)知識(shí)點(diǎn)。這里,我列出了一些關(guān)于Java集合的重要問題和答案。 ...

    cricode2122020-03-11
  • JAVA教程解決java后臺(tái)登錄前后cookie不一致問題

    解決java后臺(tái)登錄前后cookie不一致問題

    本文主要介紹了java后臺(tái)登錄前后cookie不一致的解決方案,具有很好的參考價(jià)值,需要的朋友一起來看下吧...

    胡金水4672020-07-17
  • JAVA教程Java集合和數(shù)組的區(qū)別

    Java集合和數(shù)組的區(qū)別

    本文主要介紹了Java集合和數(shù)組的區(qū)別。具有很好的參考價(jià)值,下面跟著小編一起來看下吧...

    夏日的微笑2802020-08-04
  • JAVA教程Java模版引擎Freemarker

    Java模版引擎Freemarker

    FreeMarker是一個(gè)模板引擎,一個(gè)基于模板生成文本輸出的通用工具,使用純Java編寫 FreeMarker被設(shè)計(jì)用來生成HTML Web頁(yè)面,特別是基于MVC模式的應(yīng)用程序 ...

    JocelynJiao1912020-04-21
  • JAVA教程詳解Java實(shí)現(xiàn)緩存(LRU,FIFO)

    詳解Java實(shí)現(xiàn)緩存(LRU,FIFO)

    本篇文章主要介紹了詳解Java實(shí)現(xiàn)緩存(LRU,FIFO) ,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧 ...

    liuyang03782020-09-06
  • JAVA教程springmvc攔截器登錄驗(yàn)證示例

    springmvc攔截器登錄驗(yàn)證示例

    本篇文章主要介紹了springmvc攔截器登錄驗(yàn)證示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧 ...

    書劍江山1582020-08-21
主站蜘蛛池模板: 天天爱天天操 | 精品国产三级 | 国产成人在线看 | 一级黄片毛片免费看 | 亚洲欧美福利视频 | 天堂视频在线 | 综合自拍偷拍 | 久久国产精品亚洲 | 黄色片在线 | 久久一区二区三 | 欧美日韩午夜 | 欧美日韩精品一区二区三区蜜桃 | 秋霞电影院午夜伦 | 成年人在线看 | 精品久久精品久久 | 天天躁日日躁bbbbb | 日韩欧美自拍 | 国产中文字幕在线 | 日韩在线视频一区 | 日本一区二区三区四区 | 亚洲性网 | 亚洲精品a | 欧美一区二区三区在线观看 | 国产99精品 | 日本三级视频在线观看 | 成人自拍视频 | 婷婷综合 | a级在线免费 | 色噜噜狠狠狠综合曰曰曰 | 国产一区视频网站 | 自拍偷拍一区 | 中文字幕在线观看1 | 久久国产精品一区二区 | 色婷婷精品 | 夜久久 | 日韩精品一区二区三区在线播放 | 亚洲人成网站999久久久综合 | 九九精品视频在线观看 | 视频一区二区国产 | 日韩中文字幕一区二区三区 | 精品一区av|