欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-07 14:23:27
前言 对于数据量在1千万,单个mysql数据库就可以支持,但是如果数据量大于这个数的时候,例如1亿,那么查询的性能就会很低。此时需要对数据库做水平切分,常见的做法是按照用...

前言

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

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

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

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

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

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

简单分析下 abstractroutingdatasource 的源码

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

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

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

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

实现了 datasource 的 getconnection(); 我们看看 determinetargetdatasource(); 做了什么

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

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

talk is cheap. show me the code

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

完整代码: ()

/**
 * 多数据源配置
 * 
 * @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://github.com/helloworlde/springboot-dynamicdatasource

总结

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