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,下面是核心代码
完整代码: ()
/** * 多数据源配置 * * @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
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
推荐阅读
-
spring实现动态切换、添加数据源及源码分析
-
基于spring的aop实现多数据源动态切换 博客分类: javaspring 动态切换springaopAbstractRoutingDataSource
-
Spring Cloud动态配置实现原理与源码分析
-
spring-session简介及实现原理源码分析
-
Spring整合多数据源实现动态切换的实例讲解
-
Spring Cloud动态配置实现原理与源码分析
-
Spring Cloud动态配置实现原理与源码分析
-
Spring整合多数据源实现动态切换的实例讲解
-
Springboot动态切换数据源的具体实现与原理分析
-
使用dynamic datasource springboot starter实现多数据源及源码分析