Backend Development 14 min read

Dynamic Data Source Switching in SpringBoot using AOP and Custom Annotations

This guide shows how to implement dynamic MySQL master‑slave switching in a SpringBoot 3 project using AOP and a custom @DataSource annotation, configuring Druid pools, defining a routing datasource, and automatically falling back to the master when a slave fails for high availability.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Dynamic Data Source Switching in SpringBoot using AOP and Custom Annotations

This article, based on the RuoYi source code, explains how to implement dynamic switching between MySQL master and slave databases in a SpringBoot project using AOP and custom annotations. When a slave database fails, the system automatically switches to the master to ensure high availability.

Why Switch Data Sources?

Read‑Write Separation: Use the master for writes and slaves for reads to improve performance.

Multi‑Tenant Architecture: Different tenants can be routed to different databases.

Sharding: Switch databases when operating across multiple shards.

Environment Isolation: Seamlessly switch between dev, test, and prod databases.

Flexible DB Management: Choose the appropriate data source based on runtime conditions.

Failover & High Availability: Automatically fall back to a standby database when the primary is unavailable.

How to Switch Data Sources

SpringBoot version: 3.0.4

JDK version: JDK 17

1. pom.xml dependencies

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>
<!-- aop切面 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- druid连接池 -->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.2.20</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- MybatisPlus -->
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.5.3.1</version>
</dependency>

2. Configuration files

application.yml

#application.yml
server:
  port: 8000
spring:
  profiles:
    active: druid

application-druid.yml

# 数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # 主库数据源
      master:
        url: jdbc:mysql://localhost:3306/study?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
      # 从库数据源
      slave:
        enabled: true
        url: jdbc:mysql://localhost:3306/t_lyj?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
      # 连接池参数(示例)
      initialSize: 5
      minIdle: 10
      maxActive: 20
      maxWait: 60000
      connectTimeout: 30000
      socketTimeout: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      maxEvictableIdleTimeMillis: 900000

3. DataSourceType enum

/**
 * 数据源
 * @author ruoyi
 */
public enum DataSourceType {
    /** 主库 */
    MASTER,
    /** 从库 */
    SLAVE
}

4. SpringUtils bean helper

@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
    private static ConfigurableListableBeanFactory beanFactory;
    private static ApplicationContext applicationContext;
    // ... methods for getBean, containsBean, isSingleton, getType, getAliases, getAopProxy, getRequiredProperty
}

5. Custom annotation

/**
 * 自定义多数据源切换注解
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
    DataSourceType value() default DataSourceType.MASTER;
}

6. DruidConfig – data source beans

@Configuration
public class DruidConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties) {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties) {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource) {
        Map
targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
    // setDataSource helper method omitted for brevity
}

7. DynamicDataSource implementation

/**
 * 动态数据源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource(DataSource defaultTargetDataSource, Map
targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

8. Context holder

public class DynamicDataSourceContextHolder {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
    private static final ThreadLocal
CONTEXT_HOLDER = new ThreadLocal<>();
    public static void setDataSourceType(String dsType) {
        log.info("切换到{}数据源", dsType);
        CONTEXT_HOLDER.set(dsType);
    }
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get() == null ? DataSourceType.MASTER.name() : CONTEXT_HOLDER.get();
    }
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}

9. AOP aspect

@Aspect
@Order(1)
@Component
public class DataSourceAspect {
    @Pointcut("@annotation(com.LYJ.study.DynamicDataSource.annocation.DataSource) || @within(com.LYJ.study.DynamicDataSource.annocation.DataSource)")
    public void dsPointCut() {}
    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        DataSource dataSource = getDataSource(joinPoint);
        if (dataSource != null) {
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }
        try {
            return joinPoint.proceed();
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
    public DataSource getDataSource(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        DataSource ds = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
        if (ds != null) return ds;
        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
    }
}

10. Usage in service

@Service
@RequiredArgsConstructor
@DataSource(value = DataSourceType.MASTER)
public class UserServiceImpl extends ServiceImpl
implements UserService {
    private final UserMapper userMapper;
    @Override
    @DataSource(value = DataSourceType.MASTER)
    public List
queryAll() {
        return userMapper.selectList(null);
    }
}

You can place @DataSource on classes or methods to control which database is used.

Multiple Slave Databases

To add more slaves, extend DataSourceType enum, add corresponding configuration sections in application-druid.yml , and the existing bean definitions will pick them up automatically.

Switching to Oracle

Add the Oracle driver dependency and a new slave configuration (e.g., slave3 ) with the Oracle JDBC URL. Remove the MySQL driver if it is no longer needed.

Note: Different SQL dialects may cause startup errors when switching between MySQL and Oracle.
JavaAOPMySQLDynamic Data SourceSpringBoot
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.