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. ...
随机推荐
- 14.6 kafka
14.6 kafka 为什么用消息队列 举例 比如在一个企业里,技术老大接到boss的任务,技术老大把这个任务拆分成多个小任务,完成所有的小任务就算搞定整个任务了. 那么在执行这些小任务的时候,可能有 ...
- 5.8 Go 单元测试
5.8 Go 单元测试 如果你不想后半生的美好时光都在寻找BUG中度过,那么必须写些程序用来检测产品代码的结果和预期的一样. Go语言的测试依赖于go test测试命令和一组按约定方式编写的测试函数, ...
- 情人节闷在家里做画( 安卓统计图MPAndroidChart开发 )
有些时候觉得一个人挺好的,可以更自由安排自己的时间: 有些时候觉得有个人挺好的,很多事情一个人做起来太没意思了,纵使心中澎湃,倾听的独有自己. 废话少说,直接上图 MPAndroidChart是啥 一 ...
- iperf压测linux网卡带宽
1.安装 yum install iperf --enablerepo=epel 2.启动服务端 iperf -s -i 1 3.启动客户端测试10分钟 iperf -c 172.16.3.153 - ...
- Struts2导图
- C# 数据操作系列 - 13 SugarSql初探
0. 前言 前言,暂时挥别NHibernate(虽然我突然发现这玩意还挺有意思的,不过看得人不多).大步进入了有很多小伙伴向我安利的SQLSugar,嗯,我一直叫SugarSQL,好像是这个吧? 这是 ...
- D. Almost Acyclic Graph 判断减一条边能不能得到DAG
D. Almost Acyclic Graph time limit per test 1 second memory limit per test 256 megabytes input stand ...
- BZOJ1066 网络流
拆点,将一个柱子拆成入点和出点,入点出点之间的容量就是柱子的容量 1066: [SCOI2007]蜥蜴 在一个r行c列的网格地图中有一些高度不同的石柱,一些石柱上站着一些蜥蜴,你的任务是让尽量多 ...
- centos 删除文件提示 Operation not permitted
如果文件上存在 i 标记,那肯定是删不掉的,同样这个文件也不能被编辑.可以进入 root 模式,去除这个标记: root@ubuntu:/home/barret/work# chattr -i 1.m ...
- 【C++】简介
注意:以下内容摘自文献[1],修改了部分内容. 前言 关于软件产业发展史,不妨访问“首次全面深度解密华为方舟编译器”一文,不仅详细介绍了软件产业的发展,还有华为方舟编译器产生的背景,值得一看! 1. ...