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. ...
随机推荐
- 【Redis】Set常见应用场景
微信抽奖小程序 >点击参与抽奖加入集合 SADD key {userID} >查看参与抽奖所有用户 SMEMBERS key >抽取count名中奖者 SRANGEMEMBER ke ...
- 自己写的vue底导航
<template> <div id="app"> <div class="tabbar"> <div class=& ...
- Kubernetes 基础资料
概述 这篇文章用来记录Kubernetes 的基础资料,整体以最新官方文档为准. 因为k8s整体比较偏运维,作为研发可先大致了解其概念及初级使用方式,后面重点学习点会放在service mesh is ...
- [PHP自动化-进阶]003.CURL处理Https请求访问
引言:继前文<模拟登录并采集数据>,<模拟登录带有验证码的网站>,大家对CURL基本上已经有了认识,这一讲简单的说一下请求Https. 在很多的站点,如TalkingData, ...
- [工具推荐]004.EXE签名工具SignTool使用教程
数字证书,真是个神奇的东西,可以保证软件不被修改,可以表明文件的发布日期,最重要的,可以很大程度的减少杀毒软件的误报,当然,这就要使用可信任的机构颁发的证书了. 现在要说的不是申请证书,而是如何制作自 ...
- ActiveMQ 反序列化漏洞(CVE-2015-5254)复现
1.运行漏洞环境 sudo docker-compose up -d 环境运行后,将监听61616和8161两个端口.其中61616是工作端口,消息在这个端口进行传递:8161是Web管理页面端口.访 ...
- 舵机MX-64AR与MX-28AR驱动
背景:硬件采用485通信,在tb上采购的无需收发控制的串口转RS485模块(485通信为半双工,一般情况需要控制收发模式).在使用该模块后,即可完全使用一个普通地串口来对485通信的舵机进行操作. 模 ...
- Spring boot Sample 012之spring-boot-web-upload
一.环境 1.1.Idea 2020.1 1.2.JDK 1.8 二.目的 spring boot 整合web实现文件上传下载 三.步骤 3.1.点击File -> New Project -& ...
- 前端代码高亮显示解决方案: prism
目录 1. 场景描述 2. React Prism 2.1 prismjs 库 2.2 babel-plugin-prismjs 插件 3. demo 4. 注意点 5. 参考链接 1. 场景描述 在 ...
- HttpServletRequest对象,请求行、请求头、请求体
HttpServletRequest 公共接口类HttpServletRequest继承自ServletRequest.客户端浏览器发出的请求被封装成为一个HttpServletRequest对象.对 ...