springboot 动态数据源的实现方法(Mybatis+Druid)

 更新时间:2019年01月23日 10:13:02   作者:时光沉旧了少年  
这篇文章主要介绍了springboot 动态数据源的实现方法(Mybatis+Druid),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

Spring多数据源实现的方式大概有2中,一种是新建多个MapperScan扫描不同包,另外一种则是通过继承AbstractRoutingDataSource实现动态路由。今天作者主要基于后者做的实现,且方式1的实现比较简单这里不做过多探讨。

实现方式

方式1的实现(核心代码):

@Configuration
@MapperScan(basePackages = "com.goofly.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSource1Config1 {

  @Bean(name = "dataSource1")
  @ConfigurationProperties(prefix = "spring.datasource.test1")
  @Primary
  public DataSource testDataSource() {
    return DataSourceBuilder.create().build();
  }
  // .....略

}

@Configuration
@MapperScan(basePackages = "com.goofly.test2", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSourceConfig2 {

  @Bean(name = "dataSource2")
  @ConfigurationProperties(prefix = "spring.datasource.test2")
  @Primary
  public DataSource testDataSource() {
    return DataSourceBuilder.create().build();
  }
  // .....略

}

方式2的实现(核心代码):

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
  private static final Logger log = Logger.getLogger(DynamicRoutingDataSource.class);
  
  @Override
  protected Object determineCurrentLookupKey() {
    //从ThreadLocal中取值
    return DynamicDataSourceContextHolder.get();
  }
}

第1种方式虽然实现比较加单,劣势就是不同数据源的mapper文件不能在同一包名,就显得不太灵活了。所以为了更加灵活的作为一个组件的存在,作者采用的第二种方式实现。

设计思路

  1. 当请求经过被注解修饰的类后,此时会进入到切面逻辑中。
  2. 切面逻辑会获取注解中设置的key值,然后将该值存入到ThreadLocal中
  3. 执行完切面逻辑后,会执行AbstractRoutingDataSource.determineCurrentLookupKey()方法,然后从ThreadLocal中获取之前设置的key值,然后将该值返回。
  4. 由于AbstractRoutingDataSource的targetDataSources是一个map,保存了数据源key和数据源的对应关系,所以能够顺利的找到该对应的数据源。

源码解读

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,如下:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
  
  private Map<Object, Object> targetDataSources;
  private Object defaultTargetDataSource;
  private boolean lenientFallback = true;
  private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
  private Map<Object, DataSource> resolvedDataSources;
  private DataSource resolvedDefaultDataSource;
  
    protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = determineCurrentLookupKey();
    DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
      dataSource = this.resolvedDefaultDataSource;
    }
    if (dataSource == null) {
      throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    }
    return dataSource;
  }

  /**
   * Determine the current lookup key. This will typically be
   * implemented to check a thread-bound transaction context.
   * <p>Allows for arbitrary keys. The returned key needs
   * to match the stored lookup key type, as resolved by the
   * {@link #resolveSpecifiedLookupKey} method.
   */
  protected abstract Object determineCurrentLookupKey();
  
  //........略

targetDataSources是一个map结构,保存了key与数据源的对应关系;

dataSourceLookup是一个DataSourceLookup类型,默认实现是JndiDataSourceLookup。点开该类源码会发现,它实现了通过key获取DataSource的逻辑。当然,这里可以通过setDataSourceLookup()来改变其属性,因为关于此处有一个坑,后面会讲到。

public class JndiDataSourceLookup extends JndiLocatorSupport implements DataSourceLookup {

  public JndiDataSourceLookup() {
    setResourceRef(true);
  }

  @Override
  public DataSource getDataSource(String dataSourceName) throws DataSourceLookupFailureException {
    try {
      return lookup(dataSourceName, DataSource.class);
    }
    catch (NamingException ex) {
      throw new DataSourceLookupFailureException(
          "Failed to look up JNDI DataSource with name '" + dataSourceName + "'", ex);
    }
  }

}

组件使用

多数据源

# db1
spring.datasource.master.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.master.username = root
spring.datasource.master.password = 123456
spring.datasource.master.driverClassName = com.mysql.jdbc.Driver
spring.datasource.master.validationQuery = true
spring.datasource.master.testOnBorrow = true
## db2
spring.datasource.slave.url = jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.slave.username = root
spring.datasource.slave.password = 123456
spring.datasource.slave.driverClassName = com.mysql.jdbc.Driver
spring.datasource.slave.validationQuery = true
spring.datasource.slave.testOnBorrow = true

#主数据源名称
spring.maindb=master
#mapperper包路径
mapper.basePackages =com.btps.xli.multidb.demo.mapper

单数据源

为了让使用者能够用最小的改动实现最好的效果,作者对单数据源的多种配置做了兼容。

示例配置1(配置数据源名称):

spring.datasource.master.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.master.username = root
spring.datasource.master.password = 123456
spring.datasource.master.driverClassName = com.mysql.jdbc.Driver
spring.datasource.master.validationQuery = true
spring.datasource.master.testOnBorrow = true

# mapper包路径
mapper.basePackages = com.goofly.xli.multidb.demo.mapper
# 主数据源名称
spring.maindb=master

示例配置2(不配置数据源名称):

spring.datasource.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username = root
spring.datasource.password = 123456
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.validationQuery = true
spring.datasource.testOnBorrow = true

# mapper包路径
mapper.basePackages = com.goofly.xli.multidb.demo.mapper

踩坑之路

多数据源的循环依赖

Description:

The dependencies of some of the beans in the application context form a cycle:

  happinessController (field private com.db.service.HappinessService com.db.controller.HappinessController.happinessService)
   ↓
  happinessServiceImpl (field private com.db.mapper.MasterDao com.db.service.HappinessServiceImpl.masterDao)
   ↓
  masterDao defined in file [E:\GitRepository\framework-gray\test-db\target\classes\com\db\mapper\MasterDao.class]
   ↓
  sqlSessionFactory defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class]
┌─────┐
| dynamicDataSource defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class]
↑   ↓
| firstDataSource defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class]
↑   ↓
| dataSourceInitializer

解决方案:

在Spring boot启动的时候排除DataSourceAutoConfiguration即可。如下:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DBMain {
  public static void main(String[] args) {
    SpringApplication.run(DBMain.class, args);
  }
}

但是作者在创建多数据源的时候由于并未创建多个DataSource的Bean,而是只创建了一个即需要做动态数据源的那个Bean。 其他的DataSource则直接创建实例然后存放在Map里面,然后再设置到DynamicRoutingDataSource#setTargetDataSources即可。

因此这种方式也不会出现循环依赖的问题!

动态刷新数据源

笔者在设计之初是想构建一个动态刷新数据源的方案,所以利用了SpringCloud的@RefreshScope去标注数据源,然后利用RefreshScope#refresh实现刷新。但是在实验的时候发现由Druid创建的数据源会因此而关闭,由Spring的DataSourceBuilder创建的数据源则不会发生任何变化。
​ 最后关于此也没能找到解决方案。同时思考,如果只能的可以实现动态刷新的话,那么数据源的原有连接会因为刷新而中断吗还是会有其他处理?

多数据源事务

有这么一种特殊情况,一个事务中调用了两个不同数据源,这个时候动态切换数据源会因此而失效。

翻阅了很多文章,大概找了2中解决方案,一种是Atomikos进行事务管理,但是貌似性能并不是很理想。

另外一种则是通过优先级控制,切面的的优先级必须要大于数据源的优先级,用注解@Order控制。

此处留一个坑!

git代码地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Java正则验证字串符RegexValidator类使用

    Java正则验证字串符RegexValidator类使用

    正则验证字串符是一种强大的工具,可以帮助程序员在处理字符串时轻松进行复杂匹配,本文将介绍正则表达式的概念、语法和在编程中的应用,并通过实例演示如何使用正则表达式进行字符串匹配、替换和提取等操作
    2023-11-11
  • Spring Boot 注解方式自定义Endpoint详解

    Spring Boot 注解方式自定义Endpoint详解

    这篇文章主要介绍了Spring Boot注解方式自定义Endpoint详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • java字节码框架ASM的深入学习

    java字节码框架ASM的深入学习

    这篇文章主要给大家介绍了java中字节码框架ASM的相关资料,文中介绍的非常详细,相信对大家的理解和学习具有一定的参考借鉴价值,有需要的朋友们下面来一起学习学习吧。
    2017-01-01
  • Java自动释放锁的三种实现方案

    Java自动释放锁的三种实现方案

    在笔者面试过程时,经常会被问到各种各样的锁,如乐观锁、读写锁等等,非常繁多,下面这篇文章主要给大家介绍了关于Java自动释放锁的三种实现方案,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-06-06
  • JavaSE实现图书管理系统的示例代码

    JavaSE实现图书管理系统的示例代码

    这篇博客是在学习了一部分Java基础语法之后的练习项目,通过这个小项目的练习,对Java中的类和对象,抽象类和接口等进行熟悉理解。快跟随小编一起学习学习吧
    2022-08-08
  • 深入聊聊Java内存泄露问题

    深入聊聊Java内存泄露问题

    所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中,下面这篇文章主要给大家介绍了关于Java内存泄露问题的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • Java编程二项分布的递归和非递归实现代码实例

    Java编程二项分布的递归和非递归实现代码实例

    这篇文章主要介绍了Java编程二项分布的递归和非递归实现代码实例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • Java连接SAP RFC实现数据抽取的示例详解

    Java连接SAP RFC实现数据抽取的示例详解

    这篇文章主要为大家学习介绍了Java如何连接SAP RFC实现数据抽取的功能,文中的示例代码讲解详细,具有一定的参考价值,需要的可以了解下
    2023-08-08
  • Caused by: java.lang.ClassNotFoundException: org.apache.commons.collections.Transformer异常

    Caused by: java.lang.ClassNotFoundException: org.apache.comm

    这篇文章主要介绍了Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.Type异常,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • Hadoop上Data Locality的详解

    Hadoop上Data Locality的详解

    这篇文章主要介绍了 Hadoop上Data Locality的详解的相关资料,希望通过本文能帮助到大家,让大家理解掌握这部分内容,需要的朋友可以参考下
    2017-10-10

最新评论