服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

Spring Boot 实现读写分离,还有谁不会??

日期: 来源:SpringForAll收集编辑:
关注我,回复关键字“2022面经”
领取2022大厂Java后端面经


# 第一步:配置多数据源


首先,我们在 SpringBoot 中配置两个数据源,其中第二个数据源是ro-datasource:

spring:  datasource:    jdbc-url: jdbc:mysql://localhost/test    username: rw    password: rw_password    driver-class-name: com.mysql.jdbc.Driver    hikari:      pool-name: HikariCP      auto-commit: false      ...  ro-datasource:    jdbc-url: jdbc:mysql://localhost/test    username: ro    password: ro_password    driver-class-name: com.mysql.jdbc.Driver    hikari:      pool-name: HikariCP      auto-commit: false      ...


在开发环境下,没有必要配置主从数据库。只需要给数据库设置两个用户,一个rw具有读写权限,一个ro只有 SELECT 权限,这样就模拟了生产环境下对主从数据库的读写分离。


如果您正在学习Spring Boot,那么推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/

在 SpringBoot 的配置代码中,我们初始化两个数据源:

@SpringBootApplicationpublic class MySpringBootApplication {    /**     * Master data source.     */    @Bean("masterDataSource")    @ConfigurationProperties(prefix = "spring.datasource")    DataSource masterDataSource() {       logger.info("create master datasource...");        return DataSourceBuilder.create().build();    }

/** * Slave (read only) data source. */ @Bean("slaveDataSource") @ConfigurationProperties(prefix = "spring.ro-datasource") DataSource slaveDataSource() { logger.info("create slave datasource..."); return DataSourceBuilder.create().build(); }

...}


# 第二步:编写 RoutingDataSource


然后,我们用 Spring 内置的 RoutingDataSource,把两个真实的数据源代理为一个动态数据源:

public class RoutingDataSource extends AbstractRoutingDataSource {

@Override protected Object determineCurrentLookupKey() { return "masterDataSource"; }}


对这个RoutingDataSource,需要在 SpringBoot 中配置好并设置为主数据源:

@SpringBootApplicationpublic class MySpringBootApplication {    @Bean    @Primary    DataSource primaryDataSource(            @Autowired @Qualifier("masterDataSource") DataSource masterDataSource,            @Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource    ) {        logger.info("create routing datasource...");        Map<Object, Object> map = new HashMap<>();        map.put("masterDataSource", masterDataSource);        map.put("slaveDataSource", slaveDataSource);        RoutingDataSource routing = new RoutingDataSource();        routing.setTargetDataSources(map);        routing.setDefaultTargetDataSource(masterDataSource);        return routing;    }    ...}


现在,RoutingDataSource 配置好了,但是,路由的选择是写死的,即永远返回"masterDataSource",


现在问题来了:如何存储动态选择的 key 以及在哪设置 key?


在 Servlet 的线程模型中,使用 ThreadLocal 存储 key 最合适,因此,我们编写一个 RoutingDataSourceContext,来设置并动态存储 key:

public class RoutingDataSourceContext implements AutoCloseable {

// holds data source key in thread local: static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>();

public static String getDataSourceRoutingKey() { String key = threadLocalDataSourceKey.get(); return key == null ? "masterDataSource" : key; }

public RoutingDataSourceContext(String key) { threadLocalDataSourceKey.set(key); }

public void close() { threadLocalDataSourceKey.remove(); }}


然后,修改 RoutingDataSource,获取 key 的代码如下:

public class RoutingDataSource extends AbstractRoutingDataSource {    protected Object determineCurrentLookupKey() {        return RoutingDataSourceContext.getDataSourceRoutingKey();    }}

这样,在某个地方,例如一个 Controller 的方法内部,就可以动态设置 DataSource 的 Key:

@Controllerpublic class MyController {    @Get("/")    public String index() {        String key = "slaveDataSource";        try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(key)) {            // TODO:            return "html... www.liaoxuefeng.com";        }    }}


到此为止,我们已经成功实现了数据库的动态路由访问。


这个方法是可行的,但是,需要读从数据库的地方,就需要加上一大段try (RoutingDataSourceContext ctx = ...) {}代码,使用起来十分不便。有没有方法可以简化呢?


有!


我们仔细想想,Spring 提供的声明式事务管理,就只需要一个@Transactional()注解,放在某个 Java 方法上,这个方法就自动具有了事务。


我们也可以编写一个类似的@RoutingWith("slaveDataSource")注解,放到某个 Controller 的方法上,这个方法内部就自动选择了对应的数据源。代码看起来应该像这样:

@Controllerpublic class MyController {    @Get("/")    @RoutingWith("slaveDataSource")    public String index() {        return "html... www.liaoxuefeng.com";    }}


这样,完全不修改应用程序的逻辑,只在必要的地方加上注解,自动实现动态数据源切换,这个方法是最简单的。


想要在应用程序中少写代码,我们就得多做一点底层工作:必须使用类似 Spring 实现声明式事务的机制,即用 AOP 实现动态数据源切换。


实现这个功能也非常简单,编写一个RoutingAspect,利用 AspectJ 实现一个Around拦截:

@Aspect@Componentpublic class RoutingAspect {    @Around("@annotation(routingWith)")    public Object routingWithDataSource(ProceedingJoinPoint joinPoint, RoutingWith routingWith) throws Throwable {        String key = routingWith.value();        try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(key)) {            return joinPoint.proceed();        }    }}


注意方法的第二个参数RoutingWith是 Spring 传入的注解实例,我们根据注解的value()获取配置的 key。编译前需要添加一个 Maven 依赖:

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-aop</artifactId></dependency>


到此为止,我们就实现了用注解动态选择数据源的功能。最后一步重构是用字符串常量替换散落在各处的"masterDataSource"和"slaveDataSource"。


# 使用限制


受 Servlet 线程模型的局限,动态数据源不能在一个请求内设定后再修改,也就是@RoutingWith不能嵌套。此外,@RoutingWith和@Transactional混用时,要设定 AOP 的优先级。


本文代码需要 SpringBoot 支持,JDK 1.8 编译并打开-parameters编译参数。

来源:www.liaoxuefeng.com



END


关注下方公众号,回复【JAVA宝典】
获取最新免费Java资料大全


往期推荐

Spring 注解 @bean 和 @component 的区别, 你知道吗?

基于Spring Cloud的微服务电商平台系统,这个项目手把手教你实现

Spring高频面试题,附加答案!

SpringBoot 配置文件敏感信息加密

Spring Cloud 2022.0.0 M1 发布:需Java 17、兼容Spring Boot 3

最强Java并发编程笔记详解,面试必备!


相关阅读

  • 25000 字详解 23 种设计模式(多图 + 代码)

  • 「 关注“石杉的架构笔记”,大厂架构经验倾囊相授 」文章来源:https://javadoop.com/post/design-pattern目录创建型模式结构型模式行为型模式总结前言一直想写一篇介绍设计模
  • 日志分析技巧

  • 溯源工具推荐工具:EmEditor下载地址:https://zh-cn.emeditor.com正版激活密钥:DMAZM-WHY52-AX222-ZQJXN-79JXH如何用EmEditor对整个文件夹进行查找?1、在搜索菜单中,选择“在文件
  • 7min 到 40s:SpringBoot 启动优化实践!

  • 关注我,回复关键字“spring”,免费领取Spring学习资料。来源:https://juejin.cn/post/71813425237285929550 背景公司 SpringBoot 项目在日常开发过程中发现服务启动过程异常缓
  • 柔性事务的分布式事务解决方案设计探究

  • 关注我,回复关键字“spring”,免费领取Spring学习资料。来源 | OSCHINA 社区作者 | 京东云开发者-郑朋辉原文链接:https://my.oschina.net/u/4090830/blog/55859631 背景市面上
  • Spring Boot中一个注解优雅实现重试

  • 关注我,回复关键字“spring”,免费领取Spring学习资料。重试,在项目需求中是非常常见的,例如遇到网络波动等,要求某个接口或者是方法可以最多/最少调用几次;实现重试机制,非得用Ret
  • Spring Boot 项目鉴权的 4 种方式

  • 关注我,回复关键字“spring”,免费领取Spring学习资料。文章介绍了spring-boot中实现通用auth的四种方式,包括 传统AOP、拦截器、参数解析器和过滤器,并提供了对应的实例代码,最
  • 走进加油站 为安全意识“加油”

  • 为进一步加强加油站消防宣传教育培训工作,打击冒牌消防培训违法行为,全力提升加油站员工的消防安全防范意识和应急处置能力,坚决预防和遏制各类火灾事故的发生,4月24日,高新

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四
  • 美国对华2000亿关税清单,到底影响有多大?

  • 1 今天A股大跌,上证最大跌幅超过2%。直接导火索是美国证实计划对华2000亿美元产品加征25%关税。 听起来,2000亿美元数目巨大,我们来算笔账。 2000亿美元,按现在人民币汇率

最新文章

  • Spring Boot 实现读写分离,还有谁不会??

  • 关注我,回复关键字“2022面经”,领取2022大厂Java后端面经。# 第一步:配置多数据源首先,我们在 SpringBoot 中配置两个数据源,其中第二个数据源是ro-datasource:spring: datasour
  • docsify综合漏洞知识库,持续更新!

  • 声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,未经授权,严禁转载,如需转载,联系开白。请勿利用文章内的相关技术从事非法测试
  • 防火墙 firewalld 的常用命令

  • firewalld是Linux中最长用的防火墙之一。初次配置后,后面基本没有什么多大的变动。但是想再次想要添加对应的防火墙断开,命令又忘记了。现对常用命令做一简单的汇总。方便学习
  • 云原生架构下B站Flink存算分离的改造实践

  • 本期作者张陈毅哔哩哔哩资深开发工程师曹杰哔哩哔哩资深开发工程师1. 背景在当前整个行业及公司内部降本增效的大背景下,B站内部也在积极推进实时与在线业务资源的整合,往云原