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. ...
随机推荐
- RN中的常用组件-----图片
1.RN中的常用组件-----图片 本地图片: <Image source={require('../src/assets/x.jpg')}/> 本地图片可以无需指定尺寸(因为导入/打包 ...
- DPDK Mbuf Library(学习笔记)
1 Mbuf库 Mbuf库提供了分配和释放缓冲区(mbufs)的功能,DPDK应用程序可以使用这些mbufs来存储消息缓冲. 消息缓冲存储在内存池中,使用Mempool库. 数据结构rte_mbuf通 ...
- RAID0、RAID1及RAID5的区别详解
目前已有的RAID(Redundant Array of Independent Disks,独立冗余磁盘阵列)技术有很多种,但是RAID0.RAID1.RAID5是最常见的几种方案. 1 RAID0 ...
- Storm-jdbc-2讲 高级API及Trident
之前对Storm集成JDBC写了一个简单的demo,最近深度研究了下,代码如下 首先,先写一个抽象类,便于减少代码的重复性: import com.google.common.collect.List ...
- poj2823单调队列认知
Sliding Window Time Limit: 12000MS Memory Limit: 65536K Total Submissions: 62930 Accepted: 17963 ...
- MongoDB全球云端技术盛会MongoDB.live
MongoDB全球云端技术盛会MongoDB.live,将于北京时间6月9日22:00正式开启,大会将以在线直播+按需学习相结合的方式,面向全球开发者.架构师等MongoDB 用户和爱好者免费开放,精 ...
- linux:基本指令
指令标准格式 指令主体 [选项][操作对象] 基础指令 ls 1.ls 列出当前工作目录下的所有文件/文件夹的名称 2.ls 路径 列出指定路径下的所有文件/文件夹的名称 路径: 1.相对路径 会用 ...
- HEARTBLEED 漏洞复现
漏洞分析的两篇文章 https://blog.csdn.net/javajiawei/article/details/82429886 https://xz.aliyun.com/t/1771 set ...
- ASP.NET Core Blazor Webassembly 之 数据绑定
上一次我们学习了Blazor组件相关的知识(Asp.net Core Blazor Webassembly - 组件).这次继续学习Blazor的数据绑定相关的知识.当代前端框架都离不开数据绑定技术. ...
- 网站的安全性对seo优化至关重要-智狐SEO顾问
网站的安全性对seo优化至关重要 作者:智狐zhihuseo 从大的范围来看,网站安全性能也属于seo的范畴之一.域名被恶意泛解析就是网站安全性能低下的特征之一,如果网站域名被恶意泛解析,会直接 ...