Spring之多数据源切换的应用
这不是一个新的知识点扩展,顶多算是,Spring的AOP特性的一个应用。
那么下面开始今天的学习之旅!
场景
数据库读写分离,或者分库,总之多数据源的场景,怎么样实现自动切换(PS:不考虑各种分库分表的代理中间件噢)
使用
结合场景,那么我们的目的很简单。
就是利用Spring的AOP特性,创建一个注解类修饰service 方法,通过注解切入,设置数据库来源,完成调用后,再恢复原数据库来源。
那么我们需要怎么做
- 注解类
- AOP切面类,处理此注解的
- 设置数据库来源的类
上面我们只是做到了,设置,查数据库之前,切换数据库来源。
- 数据源信息在哪一步读取
- 是怎么实现把切换后的数据源信息告诉dao层的
如果我们解决了以上问题,那么这个功能就应该可以轻松的实现咯。 这里就要提这次的重头戏
当在业务层需要涉及到查询多种同数据库的场景下,我们通常需要在执行sql的时候动态指定对应的datasource。 而Spring的AbstractRoutingDataSource则正好为我们提供了这一功能点。
先上例子吧,看完例子回后,我们再尝试着分析AbstractRoutingDataSource的来龙去脉看看能不能解答上面的2个疑点。
实例参考 这是别人的博文,这里我就不赘叙咯,免得文章拖沓,没有实质的意义。
这里仅仅贴我的核心类
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
private static String dynamicPrefix = "spring.core.datasource";
private static String filters = "Filters";
private static String connectionProperties = "ConnectionProperties";
@Override
protected Object determineCurrentLookupKey() {
log.debug("数据源为{}", DataSourceContextHolder.getDB());
return DataSourceContextHolder.getDB();
}
/**
* 构建动态多数据源
* @param route
* @param prefix
* @return
*/
public static DynamicDataSource doCreateDataSource(String route, String prefix){
if(StringUtils.isEmpty(route)){
throw new IllegalArgumentException("create route dbSource is error; route is null");
}
DynamicDataSource dynamicDataSource = new DynamicDataSource();
String[] dataNames = route.split(",");
if(null == dataNames || dataNames.length == 0){
throw new IllegalArgumentException("create route dbSource is error; Route is format error, please refer to dataSource1,dataSource2");
}
Map<Object, Object> dsMap = new HashMap(10);
//遍历数据源
String defaultDataName = null;
for(String dataName : dataNames){
if(StringUtils.isEmpty(defaultDataName)){
defaultDataName = dataName;
}
//得到当前数据源配置
Map<String, Object> paramMap = getDataSourceProperties(prefix, dataName);
if(CollectionUtils.isEmpty(paramMap)){
throw new CloudException("init database is error, param empty");
}
DruidDataSource druidDataSource = (DruidDataSource) DataSourceBuilder.create().type(DruidDataSource.class).build();
//将配置参数,注入druidDataSource对象
new RelaxedDataBinder(druidDataSource).bind(new MutablePropertyValues(paramMap));
//设置数据源名称
druidDataSource.setName(dataName);
try {
//初始化数据源
druidDataSource.init();
} catch (SQLException e) {
log.error("init database {} is error " + e.getMessage(), dataName, e);
throw new CloudException("init" +dataName +"db is error");
}
dsMap.put(dataName, druidDataSource);
}
//首个数据源为默认
dynamicDataSource.setDefaultTargetDataSource(dsMap.get(defaultDataName));
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
/**
* 根据系统参数获取数据源配置
* @param prefix
* @param dataName
* @return
*/
private static Map<String, Object> getDataSourceProperties(String prefix, String dataName){
Map<String, Object> paramMap = new HashMap<>();
//
Properties properties = GlobalRuntimeConfigFactory.getInstance().getProperties();
//prefix=spring.datasource 共有配置获取
doIterator(properties, prefix, paramMap);
prefix = dynamicPrefix + "." + dataName;
doIterator(properties, prefix, paramMap);
return paramMap;
}
/**
* 遍历配置对象launchArgs,将需要配置存入paramMap
* @param properties 配置对象
* @param prefix 需要的配置前缀
* @param paramMap 返回需要的配置
*/
private static void doIterator(Properties properties, String prefix, Map<String, Object> paramMap){
if(null == properties ){
return;
}
//launchArg格式 --key=value
for(Map.Entry<Object,Object> entry: properties.entrySet()){
if(StringUtils.isEmpty(entry)){
continue;
}
String keyName = entry.getKey().toString();
String value = entry.getValue().toString();
if (!StringUtils.isEmpty(keyName) && keyName.startsWith(prefix)) {
String key = StringUtils.toUpperCaseFirstOne(keyName.substring(prefix.length() + 1));
if (!key.contains(".")) {
paramMap.put(StringUtils.toUpperCaseFirstOne(keyName.substring(prefix.length() + 1)), value);
}
}
}
}
}
@Value("${spring.datasource.route:}")
private String route;
public static final String PREFIX = "spring.datasource";
@Bean
@Primary
@ConfigurationProperties(prefix = DataSourceConfiguration.PREFIX)
@ConditionalOnMissingClass("spring.framework.jta.configuration.PrimaryXADataSourceConfiguration")
public DataSource dataSource() {
if(StringUtils.isEmpty(route)){
return DataSourceBuilder.create().type(DruidDataSource.class).build();
// return DataSourceBuilder.create().type(SweetDataSource.class).build();
} else {
//启动动态多数据源
return DynamicDataSource.doCreateDataSource(route, PREFIX);
}
}
原理
原理,不是我总计的,还是上面那个文章的博主的原话吧
AbstractRoutingDataSource 动态路由数据源的注入原理,可以看到这个内部类里面包含了多种用于做数据源映射的map数据结构
- private Map<Object, DataSource> resolvedDataSources;
也就是上边我们所提及的使用于查询当前数据源key的方法
- protected abstract Object determineCurrentLookupKey();
而在该类的afterPropertiesSet里面,又有对于初始化数据源的注入操作,这里面的targetDataSources 正是上文中我们对在初始化数据源时候注入的信息
@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
Spring之多数据源切换的应用的更多相关文章
- spring+mybatis多数据源切换
在实际的公司项目中,很可能会遇到一个问题就是,一个java项目,但是项目中涉及两个数据库,这两个数据库还在不同IP的机子上. 遇到这种情况的时候,我们有两个选择 1.不走spring的aop方式,直接 ...
- Spring 实现动态数据源切换--转载 (AbstractRoutingDataSource)的使用
[参考]Spring(AbstractRoutingDataSource)实现动态数据源切换--转载 [参考] 利用Spring的AbstractRoutingDataSource解决多数据源的问题 ...
- spring mvc 多数据源切换,不支持事务控制[一]
一个项目中需要使用两个数据库,Oracle 和Mysql ,于是参考各个blog,实现此功能.写好后才发现,原来的事务失效了,我去... spring-mybatis.xml 配置 <bean ...
- spring+mybatis 多数据源切换失败的可能原因
可能因为,加了事务. // @Transactional(readOnly = false) // 需要事务操作必须加入此注解 就因为加了事务,导致了,问题的出现. 不然setCustomerType ...
- spring+mybatis 多数据源切换
摘自: http://www.oschina.net/code/snippet_347813_12525 1. 代码: DbContextHolder public class DbContextHo ...
- 基于Spring框架的简单多数据源切换解决办法
基于Spring框架的简单多数据源切换解决办法 Spring框架JDBC包提供了一个抽象类AbstractRoutingDataSource提供了动态切换数据库的基础方法.我们仅仅需要实现一个简单的数 ...
- Spring Boot 如何动态切换数据源
本章是一个完整的 Spring Boot 动态数据源切换示例,例如主数据库使用 lionsea 从数据库 lionsea_slave1.lionsea_slave2.只需要在对应的代码上使用 Data ...
- spring+mybatis多数据源动态切换
spring mvc+mybatis+多数据源切换 选取oracle,mysql作为例子切换数据源.oracle为默认数据源,在测试的action中,进行mysql和oracle的动态切换. web. ...
- spring mvc+mybatis+多数据源切换
spring mvc+mybatis+多数据源切换,选取oracle,mysql作为例子切换数据源.oracle为默认数据源,在测试的action中,进行mysql和oracle的动态切换. web. ...
随机推荐
- Rabbit的字符串 字符串最小表示法
Rabbit的字符串 #include<bits/stdc++.h> using namespace std; ; char s[maxn]; int get_min_pos() { , ...
- 201771010128王玉兰《面向对象程序设计(Java)》第十六周学习总结
第一部分:理论基础 1.线程的概念 进程:进程是程序的一次动态执行,它对应了从代码加 载.执行至执行完毕的一个完整过程. 多线程:多线程是进程执行过程中产生的多条执行线索. 线程:线程是比进程执行 ...
- 201771010128王玉兰《面向对象程序设计(Java)》第十三周学习总结
第一部分:基础理论知识 1.事件处理基础 事件源(event source):能够产生事件的对象都可 以成为事件源,如文本框.按钮等.一个事件源是一个 能够注册监听器并向监听器发送事件对象的对象. 事 ...
- 单词数(hdu2072)
这道题用到了(STL初步)集合:Set 的知识点.同时,也用到了stringstream 的知识点,还用到了getline(cin,line)的知识点. #include<iostream> ...
- 用jquery写一个上拉加载
/*可加载页面吗*/function canLoadMore() { return $('.loadin').length < 1;}/*移除正在加载字样*/function removeMor ...
- Maven——pom.xml文件报错:Missing artifact:jar包
原因:该错误原因为maven库中jar包无法更新 解决方法:找到maven库中对应的jar包路径,删除文件夹中的红框中的三个文件
- JS 写逻辑判断,不要只知道用 if-else 和 switch
我们在编写 JS 代码时,经常会遇到逻辑判断复杂的情况.一般情况下,可以用 if/else 或 switch 来实现多个条件判断,但会出现一个问题:随着逻辑复杂度的增加,代码中的 if/else 和 ...
- 前端自动化构建之gulp
前言 之前学完html的基础后就去学js框架了,每次都是用脚手架搭好的文件,在无形中体验了一波前端自动化带来的方便.然后前一段时间才开始学习前端自动化. 基本介绍 gulp说得简单一点就是一个自动化把 ...
- [安卓安全] 01.安卓本地数据存储:Shared Preferences安全风险浅析
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- [256个管理学理论]003.鳄鱼法则(Alligator Principle)
鳄鱼法则(Alligator Principle) 来自于大洋彼岸的让你看不懂的解释: 这是经济学交易技术法则之一,也叫“鳄鱼效应”,它的意思是:假定一只鳄鱼咬住你的脚,如果你用手去试图挣脱你的脚,鳄 ...