Backend Development 14 min read

Dynamic Data Source Switching in SpringBoot Using AOP and Custom Annotations

This article demonstrates how to implement MySQL master‑slave dynamic data source switching in a SpringBoot 3.0.4 project using AOP, custom annotations, and configuration files, ensuring automatic failover to the master when a slave becomes unavailable.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Dynamic Data Source Switching in SpringBoot Using AOP and Custom Annotations

Introduction

This guide, based on the RuoYi source code, shows how to use SpringBoot, AOP, and a custom annotation to achieve dynamic switching between MySQL master and slave databases, automatically falling back to the master when a slave fails.

Why Switch Data Sources and Application Scenarios

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 data sources when accessing different shards.

Environment Isolation : Separate databases for dev, test, and prod.

Flexible DB Management : Choose the appropriate data source at runtime based on business logic.

Failover and High Availability : Automatically switch to a standby database when the primary is down.

How to Switch Data Sources

SpringBoot version: 3.0.4

JDK version: JDK 17

pom.xml Dependencies

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>

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

<!-- Druid connection pool -->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.2.20</version>
</dependency>

<!-- MySQL driver -->
<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
</dependency>

<!-- MyBatis‑Plus -->
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.5.3.1</version>
</dependency>

Configuration Files (application.yml & application-druid.yml)

# application.yml
server:
  port: 8000
spring:
  profiles:
    active: druid
# application-druid.yml (partial)
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # Master datasource
      master:
        url: jdbc:mysql://localhost:3306/study?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
      # Slave datasource (enabled by default)
      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
        # connection pool settings ...

DataSourceType Enum

public enum DataSourceType {
    MASTER,
    SLAVE
}

SpringUtils Bean Utility

@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
    private static ConfigurableListableBeanFactory beanFactory;
    private static ApplicationContext applicationContext;
    // ... methods to get beans, check existence, etc.
}

Custom Annotation @DataSource

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
    DataSourceType value() default DataSourceType.MASTER;
}

DruidConfig Configuration Class

@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);
    }

    public void setDataSource(Map
targetDataSources, String sourceName, String beanName) {
        try {
            DataSource ds = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, ds);
        } catch (Exception e) {
            // ignore if bean not found
        }
    }
}

DynamicDataSource Core Class

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();
    }
}

DynamicDataSourceContextHolder

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("Switching to {} datasource", 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();
    }
}

AOP Aspect for DataSource Switching

@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);
    }
}

Usage in Service Layer

@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);
    }
}

The annotation can be placed on classes or individual methods to control which datasource is used.

Adding Multiple Slave Datasources

Extend DataSourceType enum with additional constants (e.g., SLAVE2 ) and add corresponding configurations in application-druid.yml . The same annotation usage applies.

Switching to Oracle

com.oracle
ojdbc6
11.2.0.3
# application-druid.yml (Oracle slave)
slave3:
  enabled: true
  url: jdbc:oracle:thin:@127.0.0.1:1521:oracle
  username: root
  password: password

Remove the MySQL driver if only Oracle is used; SpringBoot will auto‑detect the appropriate driver.

Note: Because MySQL and Oracle have different SQL dialects, startup may fail if the wrong dialect is used.

Finally, the article ends with a promotional call‑to‑action encouraging readers to share and join a community group.

JavaAOPMySQLDynamic Data SourceSpringBoot
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

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.