Mastering Dynamic MySQL Master‑Slave Switching in SpringBoot with AOP

Learn how to implement dynamic MySQL master‑slave switching in a SpringBoot project using AOP and custom annotations, ensuring automatic failover to the master when a slave fails, with detailed configuration, code examples, and guidance for multiple data sources and Oracle integration.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Mastering Dynamic MySQL Master‑Slave Switching in SpringBoot with AOP

Introduction

This article explains how to use AOP and a custom annotation in a SpringBoot project to achieve dynamic MySQL master‑slave switching. When a slave becomes unavailable, the system automatically switches to the master to maintain high availability.

If the server has one master and multiple slaves, the master handles writes while slaves handle reads. When a method is annotated to use a slave that is down, the framework automatically switches to another available datasource.

Why switch data sources? Common scenarios

Read‑write separation: Improves performance and availability by directing writes to the master and reads to slaves.

Multi‑tenant architecture: Allows each tenant to operate on a separate database.

Sharding: Distributes large data sets across multiple databases.

Environment isolation: Different databases for development, testing, and production.

Flexible DB management: Choose the appropriate datasource based on business logic.

Failover and high availability: Automatically switch to a standby database when the primary fails.

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 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>

2. Configuration files: application.yml and application‑druid.yml

application.yml

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

application‑druid.yml

# Data source configuration
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
      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
      initialSize: 5
      minIdle: 10
      maxActive: 20
      maxWait: 60000
      connectTimeout: 30000
      socketTimeout: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      maxEvictableIdleTimeMillis: 900000

3. DataSourceType enum

/**
 * Data source enum
 */
public enum DataSourceType {
    /** Master */
    MASTER,
    /** Slave */
    SLAVE
}

4. SpringUtils bean helper

@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
    private static ConfigurableListableBeanFactory beanFactory;
    private static ApplicationContext applicationContext;
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        SpringUtils.beanFactory = beanFactory;
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringUtils.applicationContext = applicationContext;
    }
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        return (T) beanFactory.getBean(name);
    }
    public static <T> T getBean(Class<T> clz) {
        return beanFactory.getBean(clz);
    }
    public static boolean containsBean(String name) {
        return beanFactory.containsBean(name);
    }
    public static boolean isSingleton(String name) {
        return beanFactory.isSingleton(name);
    }
    public static Class<?> getType(String name) {
        return beanFactory.getType(name);
    }
    public static String[] getAliases(String name) {
        return beanFactory.getAliases(name);
    }
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker) {
        return (T) AopContext.currentProxy();
    }
    public static String[] getActiveProfiles() {
        return applicationContext.getEnvironment().getActiveProfiles();
    }
    public static String getActiveProfile() {
        String[] active = getActiveProfiles();
        return active.length > 0 ? active[0] : null;
    }
    public static String getRequiredProperty(String key) {
        return applicationContext.getEnvironment().getRequiredProperty(key);
    }
}

5. Custom @DataSource annotation

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

6. 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<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
    private void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName) {
        try {
            DataSource ds = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, ds);
        } catch (Exception e) {
            // ignore missing bean
        }
    }
}

7. DynamicDataSource class

/**
 * Dynamic datasource routing implementation
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

8. DynamicDataSourceContextHolder

/**
 * Holds the current datasource identifier using ThreadLocal
 */
public class DynamicDataSourceContextHolder {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
    private static final ThreadLocal<String> 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();
    }
}

9. DataSourceAspect (AOP)

@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. Using the annotation in business code

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

The @DataSource annotation can be placed on service classes, mapper classes, or individual methods.

Additional: Multiple slave datasources

Extend application‑druid.yml with new datasource sections and add corresponding enum values.

// Add more enum values for additional slaves
public enum DataSourceType {
    MASTER,
    SLAVE,
    SLAVE2
}

Switching to Oracle

Add the Oracle driver dependency:

<!--oracle driver-->
<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc6</artifactId>
    <version>11.2.0.3</version>
</dependency>

Configure a new slave (e.g., slave3) in application‑druid.yml:

slave3:
  enabled: true
  url: jdbc:oracle:thin:@127.0.0.1:1521:oracle
  username: root
  password: password

Remove the MySQL driver; the appropriate driver will be auto‑detected. The rest of the configuration follows the same pattern, but be aware of SQL syntax differences between MySQL and Oracle.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

aopmysqlMaster‑SlaveSpringBootdynamic-datasourceDatabase Failover
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

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.