Hello,大家好,不知不觉离上一次发表文章已经4年了(抹汗)。因为工作和生活的问题,要么就是开了草稿一点没写,要么就是写了一半被打断然后续不上了或者已经过时了。不信你们看我草稿截图!真不是我偷懒啊!(抹汗)

但不管怎么说,这一次我会认真的坚持的写博客了!

为了避免同质化,我这次就分享一下R2DBC吧!

1.R2DBC介绍

R2DBC全称Reactive Relational Database Connectivity,也就是反应式关系型数据库连接。R2DBC是基于Reactive Streams标准来设计的。通过使用R2DBC,你可以使用reactive API来操作数据。

嗯?你问我为什么推荐R2DBC?嗯。。我并没有推荐哈,我只是纯粹的分享。

它确实有优点,他是支持响应式IO的,不同于JDBC的阻塞IO,性能确实有提升。但它不提供ORM框架的缓存,延迟加载,后写或其他许多功能。所以它是有好有坏的,各位需要按需使用,毕竟合适的才是最好的。

嗯?你以为我要讲R2DBC原理?不不不,这些东西你可以去别处找,我们今天就重点讲一下他的多数据源切换原理。

2.原理分析

和传统的spring-jdbc的AbstractRoutingDataSource一样,spring-r2dbc也有相同功能的AbstractRoutingConnectionFactory,以做到切换数据源。

嗯。先看下org.springframework.r2dbc.connection.lookup.AbstractRoutingConnectionFactory的核心源码。

public abstract class AbstractRoutingConnectionFactory implements ConnectionFactory, InitializingBean {private static final Object FALLBACK_MARKER = new Object();@Nullableprivate Map<?, ?> targetConnectionFactories;@Nullableprivate Object defaultTargetConnectionFactory;private boolean lenientFallback = true;private ConnectionFactoryLookup connectionFactoryLookup = new MapConnectionFactoryLookup();@Nullableprivate Map<Object, ConnectionFactory> resolvedConnectionFactories;@Nullableprivate ConnectionFactory resolvedDefaultConnectionFactory;// 需要在实现类中调用这个方法,设置一个Map<?,?>// 一般我们会将 数据源名称作为key, 数据源作为value// determineTargetConnectionFactory()就是通过这个Map来查询数据源public void setTargetConnectionFactories(Map<?, ?> targetConnectionFactories) {this.targetConnectionFactories = targetConnectionFactories;}// 需要在实现类中调用这个方法// determineTargetConnectionFactory()当获取不到数据源时// 返回这个默认的ConnectionFactorypublic void setDefaultTargetConnectionFactory(Object defaultTargetConnectionFactory) {this.defaultTargetConnectionFactory = defaultTargetConnectionFactory;}// 省略。。。// 经典spring初始化bean的必走的一个方法// 将targetConnectionFactories设置进resolvedConnectionFactories@Overridepublic void afterPropertiesSet() {Assert.notNull(this.targetConnectionFactories, "Property 'targetConnectionFactories' must not be null");this.resolvedConnectionFactories = CollectionUtils.newHashMap(this.targetConnectionFactories.size());this.targetConnectionFactories.forEach((key, value) -> {Object lookupKey = resolveSpecifiedLookupKey(key);ConnectionFactory connectionFactory = resolveSpecifiedConnectionFactory(value);this.resolvedConnectionFactories.put(lookupKey, connectionFactory);});if (this.defaultTargetConnectionFactory != null) {this.resolvedDefaultConnectionFactory = resolveSpecifiedConnectionFactory(this.defaultTargetConnectionFactory);}}// 省略。。。// 最最核心的方法protected Mono<ConnectionFactory> determineTargetConnectionFactory() {Assert.state(this.resolvedConnectionFactories != null, "ConnectionFactory router not initialized");// 通过我们实现的方法获取数据源名称Mono<Object> lookupKey = determineCurrentLookupKey().defaultIfEmpty(FALLBACK_MARKER);return lookupKey.handle((key, sink) -> {// 根据lookupKey从resolvedConnectionFactories中获取ConnectionFactoryConnectionFactory connectionFactory = this.resolvedConnectionFactories.get(key);if (connectionFactory == null && (key == FALLBACK_MARKER || this.lenientFallback)) {connectionFactory = this.resolvedDefaultConnectionFactory;}if (connectionFactory == null) {sink.error(new IllegalStateException(String.format("Cannot determine target ConnectionFactory for lookup key '%s'", key == FALLBACK_MARKER ? null : key)));return;}sink.next(connectionFactory);});}// 我们需要实现的方法,函数返回值是一个对应着自己数据源的名称protected abstract Mono<Object> determineCurrentLookupKey();}

可以看到,逻辑就是,需要先将一个Map<数据源名,数据源>通过setTargetConnectionFactories()设置好,然后再通过determineCurrentLookupKey()获取当前上下文中的数据源名称,spring就会通过determineTargetConnectionFactory()将对应的数据源注入你的dao对象中。

3.多数据源切换实现

下面贴一下我实现的子类CCRoutingConnectionFactory的代码:

public class CCRoutingConnectionFactory extends AbstractRoutingConnectionFactory {private final static Logger logger = LoggerFactory.getLogger(CCRoutingConnectionFactory.class);private final static String DB_KEY = "my_r2dbc_content_key";private final String defaultConnectionFactoryKey;private static CCConnectionFactory ccConnectionFactory;CCRoutingConnectionFactory(CCConnectionFactory ccConnectionFactory) {// 这个是我编写的自定义的ConnectionFactory,下期分享!CCRoutingConnectionFactory.ccConnectionFactory = ccConnectionFactory;Map<String, ConnectionFactory> connectionFactories = ccConnectionFactory.getConnectionFactories();this.defaultConnectionFactoryKey = ccConnectionFactory.getDefaultConnectionFactoryKey();// set target MapssetTargetConnectionFactories(connectionFactories);// set default factorysetDefaultTargetConnectionFactory(connectionFactories.get(this.defaultConnectionFactoryKey));}public static <T> Mono<T> putR2dbcSource(Mono<T> mono, String group) {return mono.contextWrite(ctx -> ctx.put(DB_KEY, ccConnectionFactory.getConnectionFactoryKey(group)));}public static <T> Flux<T> putR2dbcSource(Flux<T> flux, String group) {return flux.contextWrite(ctx -> ctx.put(DB_KEY, ccConnectionFactory.getConnectionFactoryKey(group)));}@Overrideprotected Mono<Object> determineCurrentLookupKey() {return Mono.deferContextual(Mono::just).map(ctx -> {if (ctx.hasKey(DB_KEY)) {if (logger.isDebugEnabled()) {logger.debug("determine datasource: " + ctx.get(DB_KEY));}return ctx.get(DB_KEY);} else {return this.defaultConnectionFactoryKey;}});}
}

这里补充个知识点,Webflux可以通过contextWrite(key,value)写入数据到上下文中,并且通过Mono.deferContextual()获取上下文,可以理解为以前的ThreadLocal吧(只是功能近似)。所以在上面的代码中,我是通过putR2dbcSource()的方法,将数据源名称写入到context中,然后在determineCurrentLookupKey()方法中取出上下文中存储的值,再获取到相应的ConnectionFactory。

只需要下面这样调用,就可以选择执行sql的数据源了:

1.选择“master”数据源

Flux<User> users = CCRoutingConnectionFactory.putR2dbcSource(userDAO.findAll(), "master");

2.选择“slave”数据源

Flux<User> users = CCRoutingConnectionFactory.putR2dbcSource(userDAO.findAll(), "slave");

4.添加注解和AOP,解决代码入侵问题

虽然这时候数据源已经可以自由切换了,但严谨,温柔,漂亮,帅气的同学肯定要说了:你这个对代码有入侵,老项目修改起来不方便,你个辣鸡!

博主:好好好,那我改改。。。(苦笑)

1.编写注解类,嗯,就一个value属性,用来填写数据源的名称。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface R2DBCSource {String value();
}

2.编写切面代码,嗯,也就是取注解的值,放进Context中。

@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE)
public class R2DBCSourceAOP {@Pointcut(value = "@annotation(cn.git_chinwin.cc.plugins.R2DBCSource)")public void point() {}@Around(value = "point()")public Object dynamicSelectSource(ProceedingJoinPoint pjp) {MethodSignature signature = (MethodSignature) pjp.getSignature();Method method = signature.getMethod();R2DBCSource r2DBCSource = method.getAnnotation(R2DBCSource.class);if (method.getReturnType() == Mono.class) {return CCRoutingConnectionFactory.putR2dbcSource((Mono<?>) pjp.proceed(), r2DBCSource.value());} else if (method.getReturnType() == Flux.class) {return CCRoutingConnectionFactory.putR2dbcSource((Flux<?>) pjp.proceed(), r2DBCSource.value());} else {throw new RuntimeException("不支持别的发布类型");}}
}

3.通过在方法上添加注解,即可选择数据源

    @GetMapping("/user")@R2DBCSource("master")public Flux<User> getUsers() {logger.info("~~~~~~~~~~~~~~~~~~~~~~~~");return userDAO.findAll();}

今天就先分享到这里吧,上面代码中的CCConnectionFactory这是我编写的规划数据源的初始化和分组的类,这里就不详细深入讲解了,有兴趣的同学就去我的demo看看吧。
https://github.com/chinwin94/DynamicDbSrouceWithR2dbchttps://github.com/chinwin94/DynamicDbSrouceWithR2dbc

很久没写博客了,可能写的有点乱,还望包容。

觉得写的好的话,请别吝啬你的点赞,当然,写的不好,开喷便是。

祝大家周末愉快!

2022.06.24

Spring WebFlux分享---R2DBC多数据源自由切换相关推荐

  1. 基于dynamic-datasource多数据源自由切换--多租户

    前面做了一个基于dynamic-datasource实现读写分离的实现,基于dynamic-datasource实现多数据源的读写分离_邋遢道的博客-CSDN博客 最近公司遇到一个多租户的的需求,刚好 ...

  2. SpringBoot实现多数据源,动态数据源自由切换

    业务场景 在开发中,可能涉及到在用户的业务中要去查询对应订单的数据,而用户和订单又是分处于不同的数据库的,这样的业务该怎么处理呢? 这种就是多数据源的场景,随着业务量的增大,其实这种情况还是经常能遇到 ...

  3. druid 多数据源_Spring Boot + Mybatis 中 配置Druid多数据源并实现自由切换

    概述 前面我们已经介绍过了对MyBatis.Druid的整合,接下来我们在之前的基础上做扩展,实现对Druid多数据源的配置以及动态切换数据源. 问题:多数据源使用场景有哪些呢? 回答:在业务发展中, ...

  4. Spring+Mybatis多数据源配置(四)——AbstractRoutingDataSource实现数据源动态切换

    欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...

  5. spring boot多数据源动态切换, 多数据源事务管理

    1 . 项目目标 实现 不同数据源的切换 (使用AbstractRoutingDataSource) 不同数据源之间,事物管理,多个数据源之间同时commit或者同时rollback 兼容不同的连接池 ...

  6. Spring WebFlux 实践

    文章目录 WebFlux 学习之路 1 .WebFlux 简介 2.WebFlux 的数据库操作 WebFlux 实践内容 1 .入门案例 1.1 RouterConfiguration 1.2 Ro ...

  7. Spring Boot + Mybatis 实现动态数据源

    动态数据源 在很多具体应用场景的时候,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库.又比如业务A要访问A数据库,业务B要访问B数据库等,都可以使用动 ...

  8. 响应式编程之Spring Webflux

    文章目录 一 .响应式编程 二 .响应式流 (1)JDK9响应式流: (2)Reactor响应式流库 三.Spring WebFlux 1.整合Webflux 2.事件推送 3.实现背压 四.配置数据 ...

  9. 多数据源解决方案——AOP实现多数据源动态切换

    文章目录 1. 场景描述 2. 项目依赖 3. 场景实现 4. 项目源代码 GitHub 1. 场景描述 在springboot开发中,可能遇见操作多个数据库的情形.一种解决方案就是:配置动态数据源, ...

最新文章

  1. huber loss
  2. Scrum 团队成立 -- 软件工程
  3. 在代码中向ReportViewer动态添加数据源
  4. Class 'PDO' not found 错误
  5. ASM 常用概念解释
  6. gtk+学习笔记(五)
  7. 切线理论-支撑位与阻力位
  8. ClickHouse在字节跳动推荐和广告业务部门的最佳实践
  9. Python下的云计算(OpenStack技术书籍)
  10. 纯Qt版中国象棋:实现双人对战、人机对战及网络对战
  11. 天正电气图例_天正电气设计施工图中常用线路敷设方式
  12. C#高级编程面试考题
  13. 一条sql语句查出男生前5名和女生前五名
  14. 云联惠认证时间_警方要求云联惠涉案成员限期投案,是自首寻求轻判的最佳时机...
  15. 安装office2016专业增强版
  16. 2019秋招后台开发面试记录(阿里巴巴蚂蚁金服、百度、360、美团点评)
  17. 【JS】1070- 8个工程必备的JavaScript代码片段(建议添加到项目中)
  18. 如何在 JavaScript 中格式化日期?
  19. gif透明背景动画_Gif 编辑器合集
  20. 怎么更改电脑开机密码

热门文章

  1. 「新兴技术和创新」创新网络
  2. python中条件分支语句
  3. 怎么做tiktok跨境电商,怎么监测?
  4. Linux C++工程师招聘要求汇总
  5. nao机器人Python+pycharm+naoqi平台搭建
  6. AAAI2020 一种启发式变分路径推理(VPR)脑电情绪识别方法
  7. 2011级-csdn-java-张侃— JDBC开发—连接池(一)
  8. 2011级-csdn-java-张侃—自定义JSP标签(二)
  9. WPF特效-鱼游动动画2
  10. web大作业 静态网页(地下城与勇士 10页 带视频)