首页 > 编程 > Java > 正文

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

2019-11-26 09:39:27
字体:
来源:转载
供稿:网友

前言

对于数据量在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

总结

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

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表