首页>>后端>>SpringBoot->Spring Boot 实现读写分离,还有谁不会??

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

时间:2023-11-30 本站 点击:1

第一步:配置多数据源

Spring Boot 基础就不介绍了,推荐下这个实战教程: https://github.com/javastacks/spring-boot-best-practice

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

spring:datasource:jdbc-url:jdbc:mysql://localhost/testusername:rwpassword:rw_passworddriver-class-name:com.mysql.jdbc.Driverhikari:pool-name:HikariCPauto-commit:false...ro-datasource:jdbc-url:jdbc:mysql://localhost/testusername:ropassword:ro_passworddriver-class-name:com.mysql.jdbc.Driverhikari:pool-name:HikariCPauto-commit:false...

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

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

@SpringBootApplicationpublicclassMySpringBootApplication{/***Masterdatasource.*/@Bean("masterDataSource")@ConfigurationProperties(prefix="spring.datasource")DataSourcemasterDataSource(){logger.info("createmasterdatasource...");returnDataSourceBuilder.create().build();}/***Slave(readonly)datasource.*/@Bean("slaveDataSource")@ConfigurationProperties(prefix="spring.ro-datasource")DataSourceslaveDataSource(){logger.info("createslavedatasource...");returnDataSourceBuilder.create().build();}...}

第二步:编写 RoutingDataSource

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

publicclassRoutingDataSourceextendsAbstractRoutingDataSource{@OverrideprotectedObjectdetermineCurrentLookupKey(){return"masterDataSource";}}

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

@SpringBootApplicationpublicclassMySpringBootApplication{@Bean@PrimaryDataSourceprimaryDataSource(@Autowired@Qualifier("masterDataSource")DataSourcemasterDataSource,@Autowired@Qualifier("slaveDataSource")DataSourceslaveDataSource){logger.info("createroutingdatasource...");Map<Object,Object>map=newHashMap<>();map.put("masterDataSource",masterDataSource);map.put("slaveDataSource",slaveDataSource);RoutingDataSourcerouting=newRoutingDataSource();routing.setTargetDataSources(map);routing.setDefaultTargetDataSource(masterDataSource);returnrouting;}...}

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

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

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

publicclassRoutingDataSourceContextimplementsAutoCloseable{//holdsdatasourcekeyinthreadlocal:staticfinalThreadLocal<String>threadLocalDataSourceKey=newThreadLocal<>();publicstaticStringgetDataSourceRoutingKey(){Stringkey=threadLocalDataSourceKey.get();returnkey==null?"masterDataSource":key;}publicRoutingDataSourceContext(Stringkey){threadLocalDataSourceKey.set(key);}publicvoidclose(){threadLocalDataSourceKey.remove();}}

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

publicclassRoutingDataSourceextendsAbstractRoutingDataSource{protectedObjectdetermineCurrentLookupKey(){returnRoutingDataSourceContext.getDataSourceRoutingKey();}}

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

@ControllerpublicclassMyController{@Get("/")publicStringindex(){Stringkey="slaveDataSource";try(RoutingDataSourceContextctx=newRoutingDataSourceContext(key)){//TODO:return"html...www.liaoxuefeng.com";}}}

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

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

有!

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

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

@ControllerpublicclassMyController{@Get("/")@RoutingWith("slaveDataSource")publicStringindex(){return"html...www.liaoxuefeng.com";}}

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

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

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

@Aspect@ComponentpublicclassRoutingAspect{@Around("@annotation(routingWith)")publicObjectroutingWithDataSource(ProceedingJoinPointjoinPoint,RoutingWithroutingWith)throwsThrowable{Stringkey=routingWith.value();try(RoutingDataSourceContextctx=newRoutingDataSourceContext(key)){returnjoinPoint.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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/SpringBoot/4424.html