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多数据源内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Servlet+MyBatis项目转Spring Cloud微服务,多数据源配置修改建议

    一.项目需求 在开发过程中,由于技术的不断迭代,为了提高开发效率,需要对原有项目的架构做出相应的调整. 二.存在的问题 为了不影响项目进度,架构调整初期只是把项目做了简单的maven管理,引入springboot并未做spring cloud微服务处理.但随着项目的进一步开发,急需拆分现有业务,做微服务处理.因此架构上的短板日益突出.spring cloud config 无法完全应用,每次项目部署需要修改大量配置文件.严重影响开发效率,因此便萌生了对项目架构再次调整的决心. 三.调整建议 为了

  • Springcloud+Mybatis使用多数据源的四种方式(小结)

    前段时间在做会员中心和中间件系统开发时,多次碰到多数据源配置问题,主要用到分包方式.参数化切换.注解+AOP.动态添加 这四种方式.这里做一下总结,分享下使用心得以及踩过的坑. 分包方式 数据源配置文件 在yml中,配置两个数据源,id分别为master和s1. spring: datasource: master: jdbcUrl: jdbc:mysql://192.168.xxx.xxx:xxxx/db1?......... username: xxx password: xxx drive

  • MyBatis Mapper接受参数的四种方式代码解析

    这篇文章主要介绍了MyBatis Mapper接受参数的四种方式代码解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 对于单个参数而言,可以直接写#{param},这里的占位符名称没有限制,反正就一个参数一个占位符,不需要指定名称 对于多个参数,有常用的四种方式 根据位置排序号 public interface UserDao { public Integer addUser(String username, String password)

  • Java 数组转List的四种方式小结

    目录 第一种方式(未必最佳):使用ArrayList.asList(strArray) 第二种方法(支持增删查改): 第三种方式(通过集合工具类Collections.addAll()方法(最高效)) 第四种方式通过JDK8的Stream流将3总基本类型数组转为List java数组转list误区 一.不能把基本数据类型转化为列表 二.asList方法返回的是数组的一个视图 第一种方式(未必最佳):使用ArrayList.asList(strArray) ​ 使用Arrays工具类Arrays.

  • Spring配置数据源的三种方式(小结)

    目录 一.前言 三.开发数据源的方式 方式1:手动输入 方式2:Properties配置文件 方式3:Spring配置数据源 四.总结 一.前言 今天学习了用spring配置Druid数据源的三种方式,整理了学习笔记,希望大家喜欢! 二.数据源的作用 数据源(连接池)是提高程序性能如出现的 事先实例化数据源,初始化部分连接资源 使用连接资源时从数据源中获取 使用完毕后将连接资源归还给数据源 常见的数据源:DBCP.C3P0.BoneCP.Druid等等,本文主要以Druid数据源为案例实现Spr

  • Laravel 实现Controller向blade前台模板赋值的四种方式小结

    如下所示: <?php namespace App\Http\Controllers; use Illuminate\Http\Request; class TestController extends Controller { public function show(){ return view('show',['name'=>'asdfasdfasdfa']); //方法一 是把数组里的键值对赋值过去了,blade模板里用键名来取,如{{$name}} {{$gender}} //方法二

  • 基于spring三方包类注入容器的四种方式小结

    如果引用第三方jar包,肯定是不能直接使用常用注解@Controller.@Service.@Repository.@Component将类的实例注入到spring容器中.以下四种方法可以向spring容器中导入三方包中类实例 . 1 xml配置 这种情况大家用的比较多,就是在spring的xml文件中配置需要导入的bean.在springweb项目工程web.xml中 ContextLoaderListener或者DispatcherServlet的初始参数contextConfigLocat

  • Spring中集成Groovy的四种方式(小结)

    groovy是一种动态脚本语言,适用于一些可变.和规则配置性的需求,目前Spring提供ScriptSource接口,支持两种类型,一种是 ResourceScriptSource,另一种是 StaticScriptSource,但是有的场景我们需要把groovy代码放进DB中,所以我们需要扩展这个. ResourceScriptSource:在 resources 下面写groovy类 StaticScriptSource:把groovy类代码放进XML里 DatabaseScriptSour

  • springboot集成websocket的四种方式小结

    目录 1. 原生注解 2. Spring封装 3. TIO STOMP Session 共享的问题 如何选择 其它 参考链接 1. 原生注解 pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> WebSocketConfi

  • android全局监控click事件的四种方式(小结)

    本文主要给大家分享如何在全局上去监听 click 点击事件,并做些通用处理或是拦截.使用场景可能就是具体的全局防快速重复点击,或是通用打点分析上报,用户行为监控等.以下将以四种不同的思路和实现方式去监控全局的点击操作,由简单到复杂逐一讲解. 方式一,适配监听接口,预留全局处理接口并作为所有监听器的基类使用 抽象出公共基类监听对象,可预留拦截机制和通用点击处理,简要代码如下: public abstract class CustClickListener implements View.OnCli

  • SpringMVC 接收前端传递的参数四种方式小结

    目录 SpringMVC 接收前端传递的参数四种方式 @RequestParam 获取注解 @PathVariable获取注解 SpringMVC,可以不设置任何注解即可接收参数 SpringMVC,也可以自动包装成对象 @RequestBody 用来接收数组或者复杂对象 SpringMVC的自动封装(不传参也能进入) SpringMVC接收不到前端传递的参数原因 代码清单 SpringMVC 接收前端传递的参数四种方式 @RequestParam注解 @PathVariable注解 Sprin

随机推荐