** ❃博主首页 :**
「码到三十五」
,同名公众号 :「码到三十五」,wx号 : 「liwu0213」
☠博主专栏 :
<源码解读>
<面试攻关>
♝博主的话 :搬的每块砖,皆为峰峦之基;公众号搜索「码到三十五」关注这个爱发技术干货的coder,一起筑基
微服务架构中多数据源切换是个常见的需求。Spring Boot 提供了强大的支持来简化这一过程.
多数据源切换原理
多数据源切换的原理主要基于 Spring 的
AbstractRoutingDataSource
类。
AbstractRoutingDataSource
类允许根据运行时上下文动态选择数据源。其核心在于实现
determineCurrentLookupKey
方法,该方法决定当前操作使用哪个数据源。
AbstractRoutingDataSource
实现多数据源切换的原理:
1. 数据源映射
AbstractRoutingDataSource
内部维护了一个映射(Map),用于存储数据源标识(key)和对应的数据源实例(value)。这个映射允许根据数据源标识快速查找和获取对应的数据源。
2. 数据源标识的确定
AbstractRoutingDataSource
提供了一个抽象方法determineCurrentLookupKey()
,该方法用于确定当前需要使用的数据源标识。这个方法需要由子类实现,以返回当前线程或请求应该使用的数据源标识。
3. 数据源的选择与连接获取
- 当应用程序需要获取数据库连接时,
AbstractRoutingDataSource
的getConnection()
方法会被调用。这个方法首先调用determineCurrentLookupKey()
方法来获取当前的数据源标识,然后根据这个标识从内部映射中查找对应的数据源。 - 一旦找到了对应的数据源,
AbstractRoutingDataSource
就会调用该数据源的getConnection()
方法来获取实际的数据库连接,并将这个连接返回给应用程序。
4. 数据源切换的实现
- 为了实现数据源的动态切换,通常会在子类中重写
determineCurrentLookupKey()
方法,并根据当前的上下文(如线程变量)来确定返回的数据源标识。 - 此外,通常会使用
ThreadLocal
来存储每个线程的数据源标识,这样每个线程都可以独立地切换数据源而不会互相干扰。
实现步骤
1. 依赖
引入 MySQL 和 Druid 的依赖
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.6</version></dependency>
2. 配置数据源
在
application.yml
文件中配置多个数据源。
spring:datasource:type: com.alibaba.druid.pool.DruidDataSource
druid:datasource1:url: jdbc:mysql://localhost:3306/master_db
username: root
password: password
initial-size:5min-idle:5max-active:20max-wait:60000datasource2:url: jdbc:mysql://localhost:3306/slave_db
username: root
password: password
initial-size:5min-idle:5max-active:20max-wait:60000
3. 创建数据源配置类
创建一个配置类来定义数据源 Bean。
@ConfigurationpublicclassDataSourceConfig{@Bean@ConditionalOnProperty(prefix ="spring.datasource.druid", name ="datasource1")@ConfigurationProperties(prefix ="spring.datasource.druid.datasource1")publicDataSourcedataSource1(){returnDruidDataSourceBuilder.create().build();}@Bean@ConditionalOnProperty(prefix ="spring.datasource.druid", name ="datasource2")@ConfigurationProperties(prefix ="spring.datasource.druid.datasource2")publicDataSourcedataSource2(){returnDruidDataSourceBuilder.create().build();}@Bean@PrimarypublicDataSourcedynamicDataSource(){DynamicDataSource dynamicDataSource =newDynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSource1());Map<Object,Object> targetDataSources =newHashMap<>();
targetDataSources.put("dataSource1",dataSource1());
targetDataSources.put("dataSource2",dataSource2());
dynamicDataSource.setTargetDataSources(targetDataSources);return dynamicDataSource;}}
4. 实现
AbstractRoutingDataSource
创建一个继承自
AbstractRoutingDataSource
的类,并实现
determineCurrentLookupKey
方法。
publicclassDynamicDataSourceextendsAbstractRoutingDataSource{@OverrideprotectedObjectdetermineCurrentLookupKey(){returnDataSourceContextHolder.getDataSource();}}
5. 创建
DataSourceContextHolder
创建一个工具类来保存当前线程的数据源信息。
publicclassDataSourceContextHolder{privatestaticfinalThreadLocal<String> CONTEXT_HOLDER =newThreadLocal<>();publicstaticvoidsetDataSource(String dataSource){
CONTEXT_HOLDER.set(dataSource);}publicstaticStringgetDataSource(){return CONTEXT_HOLDER.get();}publicstaticvoidclearDataSource(){
CONTEXT_HOLDER.remove();}}
6. AOP动态切换数据源
使用 AOP 在方法执行前后切换数据源。
importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.annotation.AfterReturning;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Before;importorg.aspectj.lang.annotation.Pointcut;importorg.springframework.stereotype.Component;@Aspect@ComponentpublicclassDataSourceAspect{// 方法或者类上的横切点@Pointcut("@annotation(dataSource) || @within(dataSource)")publicvoiddataSourcePointcut(DataSource dataSource){}@Before("dataSourcePointcut(dataSource)")publicvoidswitchDataSource(JoinPoint joinPoint,DataSource dataSource){// 从注解中获取数据源标识String dataSourceKey = dataSource.value();// 切换到指定的数据源DataSourceContextHolder.setDataSourceType(dataSourceKey);}@AfterReturning(pointcut ="dataSourcePointcut(dataSource)", returning ="result")publicvoidrestoreDataSource(JoinPoint joinPoint,DataSource dataSource,Object result){// 恢复默认数据源(可选)DataSourceContextHolder.clearDataSourceType();}}/**
* 自定义注解
*/@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public@interfaceDataSource{Stringvalue()default"dataSource1";}
7. 使用自定义注解
在需要切换数据源的方法上使用自定义注解。
@ServicepublicclassUserService{@AutowiredprivateUserRepository userRepo;@DataSource("dataSource2")publicvoidqueryAndUpdate(List<Integer> deptIds){List<User> userList = userRepo.findAll();System.out.println(userList.size());// 其他操作}}
注意
- 数据源切换的逻辑应该尽可能简单和高效,以避免对应用程序性能产生负面影响。
- 在切换数据源时,需要注意事务管理的问题,确保在同一个事务中只使用同一个数据源。
关注公众号[码到三十五]获取更多技术干货 !
版权归原作者 码到三十五 所有, 如有侵权,请联系我们删除。