背景:在微服务或大型项目中,经常会遇到数据库多数据源主从库读写分离的业务场景,架构上设计引发的业务问题就是有些场景下对数据的实时性要求较强,需要实时读取到数据库新增的数据,代码跑的又快,往往主从库的数据增删改操作导致的数据更新还没有及时同步读取了从库的历史数据,导致后续的业务拆分细节出现脏数据。所以,为了解决这部分问题,引入了自定义注解@DbForceMaster
(自己定义什么都行)。
具体流程是:
- 定义自定义注解,命名为DbForceMaster
- 处理自定义注解,拦截器或者AOP调用
- 配置多数据源,完成注解的主从数据库切换
- 代码中引入自定义注解
-
首先是创建一个注解类,命名为
DbForceMaster
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DbForceMaster { // 如果需要,可以添加注解属性,默认运行时方法上使用 boolean flag() default true; }
-
然后就是处理自定义注解,这部分方法就自己定义,用
HandlerMethodInterceptor
拦截器或者是AOP
面向切面编程都行,保证在方法引入@DbForceMaster注解时切换到主库数据源@Aspect @Component public class MasterDatabaseSwitchInterceptor { @Autowired private DataSourceSelector dataSourceSelector; // 假设有一个数据源选择器 @Around("@annotation(DbForceMaster)") public Object processWithMaster(ProceedingJoinPoint joinPoint) throws Throwable { if (dbForceMaster.flag()) { DbForceMasterContextHolder.setForceMasterFlag(true); } try { return joinPoint.proceed(); } finally { DbForceMasterContextHolder.clearForceMasterFlag(); } } }
-
配置多数据源,在项目中配置多个数据源(主从库),并实现一个
DataSource
切换器,可以基于某种策略选择主库或者从库@Configuration public class DataSourceConfig { public static final Integer HASH_MAP_DEFAULT_SIZE = 16; @Primary // 标记为主数据源 @Bean(name = "masterDataSource") public DataSource masterDataSource() { // 配置主库数据源的代码 } @Bean(name = "slaveDataSource") public DataSource slaveDataSource() { // 配置从库数据源的代码 } @Bean public DataSourceRouter dataSourceRouter(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) { // 创建一个数据源路由器,实现切换逻辑 DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(MapConsts.HASH_MAP_DEFAULT_SIZE); targetDataSources.put(DataSourceTypeEnum.MASTER.getType(), masterDataSource()); targetDataSources.put(DataSourceTypeEnum.SLAVE.getType(), slaveDataSource()); dynamicDataSource.setDefaultTargetDataSource(masterDataSource()); dynamicDataSource.setTargetDataSources(targetDataSources); return dynamicDataSource; } @Bean public DataSourceSelector dataSourceSelector(DataSourceRouter dataSourceRouter) { return new DataSourceSelector(dataSourceRouter); } // DataSourceSelector 类应实现切换数据源的方法 }
-
使用自定义注解,在强制使用主库数据的方法上引入自定义注解
@DbForceMaster
@DbForceMaster public Data someMethod() { // 此处执行的SQL查询将强制从主库读取数据 return repository.fetchCriticalData(); }
这只是一个简单的自定义注解设计流程,基本思路就是编辑注解,处理注解,配置数据源和切换机制,使用注解。在微服务nacos项目中使用到了,解决了订单-工单-任务拆解过程中数据读取到了从库的脏数据,没有实时获取新增的主库数据,导致业务测试出现问题。