spring实现动态切换、添加数据源及源码分析

 更新时间:2018年09月12日 14:51:29   作者:殷天文  
这篇文章主要给大家介绍了关于spring实现动态切换、添加数据源及源码分析的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

对于数据量在1千万,单个mysql数据库就可以支持,但是如果数据量大于这个数的时候,例如1亿,那么查询的性能就会很低。此时需要对数据库做水平切分,常见的做法是按照用户的账号进行hash,然后选择对应的数据库。

最近公司项目需求,由于要兼容老系统的数据库结构,需要搭建一个 可以动态切换、添加数据源的后端服务。

参考了过去的项目,通过配置多个SqlSessionFactory 来实现多数据源,这么做的话,未免过于笨重,而且无法实现动态添加数据源这个需求

通过 spring AbstractRoutingDataSource 为我们抽象了一个 DynamicDataSource 解决这一问题

简单分析下 AbstractRoutingDataSource 的源码

targetDataSources 就是我们的多个数据源,在初始化的时候会调用afterPropertiesSet(),去解析我们的数据源 然后 put 到 resolvedDataSources


实现了 DataSource 的 getConnection(); 我们看看 determineTargetDataSource(); 做了什么


通过下面的 determineCurrentLookupKey();(这个方法需要我们实现) 返回一个key,然后从 resolvedDataSources (其实也就是 targetDataSources) 中 get 一个数据源,实现了每次调用 getConnection(); 打开连接 切换数据源,如果想动态添加的话 只需要重新 set targetDataSources 再调用 afterPropertiesSet() 即可

Talk is cheap. Show me the code

我使用的springboot版本为 1.5.x,下面是核心代码

完整代码:https://gitee.com/yintianwen7/spring-dynamic-datasource (本地下载)

/**
 * 多数据源配置
 * 
 * @author Taven
 *
 */
@Configuration
@MapperScan("com.gitee.taven.mapper")
public class DataSourceConfigurer {

 /**
  * DataSource 自动配置并注册
  *
  * @return data source
  */
 @Bean("db0")
 @Primary
 @ConfigurationProperties(prefix = "datasource.db0")
 public DataSource dataSource0() {
  return DruidDataSourceBuilder.create().build();
 }

 /**
  * DataSource 自动配置并注册
  *
  * @return data source
  */
 @Bean("db1")
 @ConfigurationProperties(prefix = "datasource.db1")
 public DataSource dataSource1() {
  return DruidDataSourceBuilder.create().build();
 }

 /**
  * 注册动态数据源
  * 
  * @return
  */
 @Bean("dynamicDataSource")
 public DataSource dynamicDataSource() {
  DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
  Map<Object, Object> dataSourceMap = new HashMap<>();
  dataSourceMap.put("dynamic_db0", dataSource0());
  dataSourceMap.put("dynamic_db1", dataSource1());
  dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource0());// 设置默认数据源
  dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
  return dynamicRoutingDataSource;
 }

 /**
  * Sql session factory bean.
  * Here to config datasource for SqlSessionFactory
  * <p>
  * You need to add @{@code @ConfigurationProperties(prefix = "mybatis")}, if you are using *.xml file,
  * the {@code 'mybatis.type-aliases-package'} and {@code 'mybatis.mapper-locations'} should be set in
  * {@code 'application.properties'} file, or there will appear invalid bond statement exception
  *
  * @return the sql session factory bean
  */
 @Bean
 @ConfigurationProperties(prefix = "mybatis")
 public SqlSessionFactoryBean sqlSessionFactoryBean() {
  SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  // 必须将动态数据源添加到 sqlSessionFactoryBean
  sqlSessionFactoryBean.setDataSource(dynamicDataSource());
  return sqlSessionFactoryBean;
 }

 /**
  * 事务管理器
  *
  * @return the platform transaction manager
  */
 @Bean
 public PlatformTransactionManager transactionManager() {
  return new DataSourceTransactionManager(dynamicDataSource());
 }
}

通过 ThreadLocal 获取线程安全的数据源 key

package com.gitee.taven.config;

public class DynamicDataSourceContextHolder {

 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
  @Override
  protected String initialValue() {
   return "dynamic_db0";
  }
 };

 /**
  * To switch DataSource
  *
  * @param key the key
  */
 public static void setDataSourceKey(String key) {
  contextHolder.set(key);
 }

 /**
  * Get current DataSource
  *
  * @return data source key
  */
 public static String getDataSourceKey() {
  return contextHolder.get();
 }

 /**
  * To set DataSource as default
  */
 public static void clearDataSourceKey() {
  contextHolder.remove();
 }
}

动态 添加、切换数据源

/**
 * 动态数据源
 * 
 * @author Taven
 *
 */
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

 private final Logger logger = LoggerFactory.getLogger(getClass());

 private static Map<Object, Object> targetDataSources = new HashMap<>();
 
 /**
  * 设置当前数据源
  *
  * @return
  */
 @Override
 protected Object determineCurrentLookupKey() {
  logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
  return DynamicDataSourceContextHolder.getDataSourceKey();
 }
 
 @Override
 public void setTargetDataSources(Map<Object, Object> targetDataSources) {
  super.setTargetDataSources(targetDataSources);
  DynamicRoutingDataSource.targetDataSources = targetDataSources;
 }
 
 /**
  * 是否存在当前key的 DataSource
  * 
  * @param key
  * @return 存在返回 true, 不存在返回 false
  */
 public static boolean isExistDataSource(String key) {
  return targetDataSources.containsKey(key);
 }
 
 /**
  * 动态增加数据源
  * 
  * @param map 数据源属性
  * @return
  */
 public synchronized boolean addDataSource(Map<String, String> map) {
  try {
   Connection connection = null;
   // 排除连接不上的错误
   try { 
    Class.forName(map.get(DruidDataSourceFactory.PROP_DRIVERCLASSNAME));
    connection = DriverManager.getConnection(
      map.get(DruidDataSourceFactory.PROP_URL), 
      map.get(DruidDataSourceFactory.PROP_USERNAME),
      map.get(DruidDataSourceFactory.PROP_PASSWORD));
    System.out.println(connection.isClosed());
   } catch (Exception e) {
    return false;
   } finally {
    if (connection != null && !connection.isClosed()) 
     connection.close();
   }
   String database = map.get("database");//获取要添加的数据库名
   if (StringUtils.isBlank(database)) return false;
   if (DynamicRoutingDataSource.isExistDataSource(database)) return true; 
   DruidDataSource druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(map);
   druidDataSource.init();
   Map<Object, Object> targetMap = DynamicRoutingDataSource.targetDataSources;
   targetMap.put(database, druidDataSource);
   // 当前 targetDataSources 与 父类 targetDataSources 为同一对象 所以不需要set
//   this.setTargetDataSources(targetMap);
   this.afterPropertiesSet();
   logger.info("dataSource {} has been added", database);
  } catch (Exception e) {
   logger.error(e.getMessage());
   return false;
  }
  return true;
 } 
}

可以通过 AOP 或者 手动 DynamicDataSourceContextHolder.setDataSourceKey(String key) 切换数据源

需要注意的:当我们开启了事务之后,是无法在去切换数据源的

本文项目源码:https://gitee.com/yintianwen7/spring-dynamic-datasource (本地下载)

参考文献:https://github.com/helloworlde/SpringBoot-DynamicDataSource

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

相关文章

  • Java实现接月饼小游戏的示例代码

    Java实现接月饼小游戏的示例代码

    本文将用Java语言自制一个小游戏:《接月饼小游戏》,游戏规则为不要被月亮砸到,尽可能地多接月饼。文中的实现代码讲解清晰,感兴趣的可以动手试一试
    2022-02-02
  • ibatis迁移到mybatis3的注意事项

    ibatis迁移到mybatis3的注意事项

    这篇文章主要介绍了ibatis迁移到mybatis3的注意事项的相关资料,需要的朋友可以参考下
    2017-10-10
  • Spring Data JPA中的Specification动态查询详解

    Spring Data JPA中的Specification动态查询详解

    Specification是一个设计模式,用于企业级应用开发中,其主要目的是将业务规则从业务逻辑中分离出来,在数据查询方面,Specification可以定义复杂的查询,使其更易于重用和测试,这篇文章主要介绍了Spring Data JPA中的Specification动态查询详解,需要的朋友可以参考下
    2023-07-07
  • 简单了解java标识符的作用和命名规则

    简单了解java标识符的作用和命名规则

    这篇文章主要介绍了简单了解java标识符的作用和命名规则,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • 源码分析SpringMvc日志打印被忽略输出问题

    源码分析SpringMvc日志打印被忽略输出问题

    这篇文章主要为大家介绍了源码分析SpringMvc日志打印被忽略输出问题,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • 关于Java整合RabbitMQ实现生产消费的7种通讯方式

    关于Java整合RabbitMQ实现生产消费的7种通讯方式

    这篇文章主要介绍了关于Java整合RabbitMQ实现生产消费的7种通讯方式,消息中间件是基于队列与消息传递技术,在网络环境中为应用系统提供同步或异步、可靠的消息传输的支撑性软件系统,需要的朋友可以参考下
    2023-05-05
  • Java实现stream的三个常用方式(toMap,groupingBy,findFirst)

    Java实现stream的三个常用方式(toMap,groupingBy,findFirst)

    本文主要介绍了Java实现stream的三个常用方式,主要包括toMap,groupingBy,findFirst,具有一定的参考价值,感兴趣的可以了解一下
    2023-10-10
  • servlet配置方法及其生命周期详解

    servlet配置方法及其生命周期详解

    下面小编就为大家带来一篇servlet配置方法及其生命周期详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • java中关于内部类的使用详解

    java中关于内部类的使用详解

    本篇文章介绍了,在java中关于内部类的使用详解。需要的朋友 参考下
    2013-04-04
  • Spring Cloud Stream如何实现服务之间的通讯

    Spring Cloud Stream如何实现服务之间的通讯

    这篇文章主要介绍了Spring Cloud Stream如何实现服务之间的通讯,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10

最新评论