Backend Development 13 min read

Implementing Dynamic MySQL Master‑Slave Data Source Switching in SpringBoot with AOP and Custom Annotations

This tutorial explains how to use SpringBoot, AOP, and a custom @DataSource annotation to dynamically switch between MySQL master and slave databases—covering configuration, code implementation, multiple slave handling, and an Oracle example—to ensure high availability and flexible data source management.

Java Captain
Java Captain
Java Captain
Implementing Dynamic MySQL Master‑Slave Data Source Switching in SpringBoot with AOP and Custom Annotations

This guide, based on the RuoYi source, shows how to use AOP and a custom @DataSource annotation in a SpringBoot project to achieve dynamic switching between MySQL master and slave databases, ensuring high availability when a slave fails.

Why switch data sources: Scenarios include read/write separation, multi‑tenant architecture, sharding, environment isolation, flexible database management, and fault tolerance for high availability.

How to switch: The project uses SpringBoot 3.0.4 and JDK 17. The pom.xml must include Lombok, spring-boot-starter-aop , druid-spring-boot-starter , MySQL driver, and MyBatis‑Plus.

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>
<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 defines the active profile, while application-druid.yml configures the master and slave data sources and connection‑pool parameters.

# master datasource
spring:
  datasource:
    druid:
      master:
        url: jdbc:mysql://localhost:3306/study?... 
        username: root
        password: 123456
# slave datasource (enabled)
      slave:
        enabled: true
        url: jdbc:mysql://localhost:3306/t_lyj?... 
        username: root
        password: 123456
        initialSize: 5
        minIdle: 10
        maxActive: 20
        maxWait: 60000
        ...

Data source enumeration:

public enum DataSourceType { MASTER, SLAVE }

SpringUtils bean helper provides static methods to obtain beans, check bean existence, and access environment properties.

@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
    private static ConfigurableListableBeanFactory beanFactory;
    private static ApplicationContext applicationContext;
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) { beanFactory = bf; }
    @Override
    public void setApplicationContext(ApplicationContext ctx) { applicationContext = ctx; }
    @SuppressWarnings("unchecked")
    public static
T getBean(String name) { return (T) beanFactory.getBean(name); }
    // other utility methods omitted for brevity
}

Custom @DataSource annotation allows specifying the target data source at class or method level, with method‑level priority.

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

DruidConfig defines beans for master and slave DataSource objects and registers a DynamicDataSource bean that holds the routing map.

@Configuration
public class DruidConfig {
    @Bean @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties props) { ... }
    @Bean @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix="spring.datasource.druid.slave", name="enabled", havingValue="true")
    public DataSource slaveDataSource(DruidProperties props) { ... }
    @Bean(name="dynamicDataSource") @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource) {
        Map
target = new HashMap<>();
        target.put(DataSourceType.MASTER.name(), masterDataSource);
        // slave will be added later via setDataSource()
        return new DynamicDataSource(masterDataSource, target);
    }
    // setDataSource helper omitted for brevity
}

DynamicDataSource extends AbstractRoutingDataSource and determines the current lookup key from DynamicDataSourceContextHolder .

public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource(DataSource defaultTarget, Map
targets) {
        super.setDefaultTargetDataSource(defaultTarget);
        super.setTargetDataSources(targets);
        super.afterPropertiesSet();
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

DynamicDataSourceContextHolder uses a ThreadLocal to store the current data source name, with methods to set, get (defaulting to MASTER ), and clear the value.

public class DynamicDataSourceContextHolder {
    private static final ThreadLocal
CONTEXT_HOLDER = new ThreadLocal<>();
    public static void setDataSourceType(String ds) { CONTEXT_HOLDER.set(ds); }
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get() == null ? DataSourceType.MASTER.name() : CONTEXT_HOLDER.get();
    }
    public static void clearDataSourceType() { CONTEXT_HOLDER.remove(); }
}

AOP aspect ( DataSourceAspect ) defines a pointcut on @DataSource annotations, switches the data source before method execution, and clears it afterwards.

@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 ds = getDataSource(joinPoint);
        if (ds != null) {
            DynamicDataSourceContextHolder.setDataSourceType(ds.value().name());
        }
        try {
            return joinPoint.proceed();
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
    private DataSource getDataSource(ProceedingJoinPoint point) {
        MethodSignature sig = (MethodSignature) point.getSignature();
        DataSource ds = AnnotationUtils.findAnnotation(sig.getMethod(), DataSource.class);
        if (ds != null) return ds;
        return AnnotationUtils.findAnnotation(sig.getDeclaringType(), DataSource.class);
    }
}

Usage: Annotate service or mapper classes/methods with @DataSource(value = DataSourceType.MASTER) or SLAVE . The example shows a UserServiceImpl using the annotation.

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

For multiple slaves, extend the DataSourceType enum (e.g., SLAVE2 ) and add corresponding sections in application-druid.yml and bean definitions in DruidConfig .

Oracle example: Add the Oracle JDBC driver dependency and configure an additional slave ( slave3 ) in application-druid.yml . Remove the MySQL driver if it is not needed.

<dependency>
  <groupId>com.oracle</groupId>
  <artifactId>ojdbc6</artifactId>
  <version>11.2.0.3</version>
</dependency>
slave3:
  enabled: true
  url: jdbc:oracle:thin:@127.0.0.1:1521:oracle
  username: root
  password: password

Note: Switching between MySQL and Oracle may cause SQL syntax differences that can lead to startup errors.

JavaAOPMySQLMaster‑SlaveSpringBootmulti-datasourceDynamic DataSource
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

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.