Backend Development 15 min read

Implementing Dynamic MySQL Master‑Slave Switching in SpringBoot Using AOP and Custom Annotations

This tutorial explains how to implement dynamic master‑slave MySQL switching in a SpringBoot 3.0.4 project using AOP and a custom @DataSource annotation, covering configuration, enum definitions, utility classes, aspect handling, and usage examples for both single and multiple data sources, including Oracle integration.

Top Architecture Tech Stack
Top Architecture Tech Stack
Top Architecture Tech Stack
Implementing Dynamic MySQL Master‑Slave Switching in SpringBoot Using AOP and Custom Annotations

Preface

This article introduces how to use AOP and a custom annotation in a SpringBoot project to achieve dynamic switching of MySQL master‑slave databases, automatically falling back to the master when a slave fails to ensure high availability.

Why Switch Data Sources? What Scenarios?

Read‑Write Separation: Master handles writes, slaves handle reads to improve performance and availability.

Multi‑Tenant Architecture: Different tenants may need to operate on different databases; dynamic switching selects the appropriate source based on tenant identity.

Sharding (Database Partitioning): Large‑scale data can be split across databases; dynamic switching directs queries to the correct shard.

Environment Isolation: Development, testing, and production may use different databases; dynamic switching ensures proper isolation.

Flexible Database Management: Business logic can decide which data source to use at runtime.

Failover and High Availability: When the primary database is unavailable, the system can automatically switch to a standby source.

How to Switch Data Sources?

SpringBoot version: 3.0.4

JDK version: JDK 17

1. pom.xml

<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 & application‑druid.yml

# application.yml
server:
  port: 8000
spring:
  profiles:
    active: druid
# application‑druid.yml (MySQL master & slave example)
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)
      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

/**
 * Data source enumeration
 */
public enum DataSourceType {
    /** Master */
    MASTER,
    /** Slave */
    SLAVE,
    /** Additional slave */
    SLAVE2
}

4. Bean Utility Class: SpringUtils

@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
    private static ConfigurableListableBeanFactory beanFactory;
    private static ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtils.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }

    @SuppressWarnings("unchecked")
    public static
T getBean(String name) throws BeansException {
        return (T) beanFactory.getBean(name);
    }

    public static
T getBean(Class
clz) throws BeansException {
        return beanFactory.getBean(clz);
    }

    public static boolean containsBean(String name) {
        return beanFactory.containsBean(name);
    }

    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.isSingleton(name);
    }

    public static Class
getType(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getType(name);
    }

    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getAliases(name);
    }

    public static
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 != null && active.length > 0) ? active[0] : null;
    }

    public static String getRequiredProperty(String key) {
        return applicationContext.getEnvironment().getRequiredProperty(key);
    }
}

5. Custom Annotation: @DataSource

/**
 * Custom annotation for dynamic data source switching.
 * Method level overrides class level.
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
    DataSourceType value() default DataSourceType.MASTER;
}

6. DruidConfig Class

@Configuration
public class DruidConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties) {
        DruidDataSource ds = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(ds);
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties) {
        DruidDataSource ds = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(ds);
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource) {
        Map
target = new HashMap<>();
        target.put(DataSourceType.MASTER.name(), masterDataSource);
        setDataSource(target, DataSourceType.SLAVE.name(), "slaveDataSource");
        return new DynamicDataSource(masterDataSource, target);
    }

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

7. DynamicDataSource Core Class

/**
 * Dynamic routing datasource.
 */
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. DynamicDataSourceContextHolder

/**
 * Thread‑local holder for the current datasource key.
 */
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();
    }
}

9. AOP Aspect: DataSourceAspect

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

    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(DataSourceType.MASTER)
public class UserServiceImpl extends ServiceImpl
implements UserService {
    private final UserMapper userMapper;

    @Override
    @DataSource(DataSourceType.MASTER)
    public List
queryAll() {
        return userMapper.selectList(null);
    }
}

Both service classes and mapper methods can be annotated to specify the target datasource.

Additional: Multiple Slave Configuration

To add more slaves, extend application‑druid.yml with new datasource sections, add corresponding enum values (e.g., SLAVE2 ), and register them in DruidConfig using the same setDataSource helper.

How to Switch to Oracle

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

Remove the MySQL driver when switching to Oracle; SpringBoot will auto‑detect the appropriate driver. Be aware of SQL syntax differences that may cause startup errors.

Note: Switching databases may cause startup errors due to differences between MySQL and Oracle SQL syntax.

Author & Source

Author: 小杰不秃头 (Xiao Jie)

Source: blog.csdn.net/cyuyanya__/article/details/139605809

Promotional Content (Non‑Technical)

Free IDEA/PyCharm activation codes can be obtained by replying "C1" to the public account. JetBrains paid licenses, plugins, and themes are also offered; contact the author for details.

JavaAOPMySQLSpringBootDynamic DataSource
Top Architecture Tech Stack
Written by

Top Architecture Tech Stack

Sharing Java and Python tech insights, with occasional practical development tool tips.

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.