1、简述
在实际的业务场景中,我们经常会遇到以下需求:
🔹 读写分离:写操作走主库,读操作走从库。
🔹 多租户系统:不同租户对应不同数据库。
🔹 分库分表场景:根据业务 ID 动态路由到不同的数据源。
Spring Boot + MyBatis 可以结合 AbstractRoutingDataSource 实现动态数据源切换。本文将结合一个 主从数据库读写分离 的案例,演示 MyBatis 动态数据源的实现。

2、核心思路
🔹 定义一个 动态数据源上下文,保存当前线程使用的数据源标识(ThreadLocal)。
🔹 自定义一个继承 AbstractRoutingDataSource 的类,实现数据源路由逻辑。
🔹 配置多个真实数据源(如 master、slave)。
🔹 通过 AOP 或注解,在方法执行前设置数据源上下文。
🔹 MyBatis 查询时自动路由到对应数据源。
3、实践案例
3.1 定义数据源标识
public enum DataSourceType {
    MASTER, SLAVE
}
3.2 动态数据源上下文
public class DataSourceContextHolder {
    private static final ThreadLocal<DataSourceType> CONTEXT = new ThreadLocal<>();
    public static void set(DataSourceType type) {
        CONTEXT.set(type);
    }
    public static DataSourceType get() {
        return CONTEXT.get() == null ? DataSourceType.MASTER : CONTEXT.get();
    }
    public static void clear() {
        CONTEXT.remove();
    }
}
3.3 动态数据源实现类
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.get();
    }
}
3.4 配置数据源
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
    @Bean(name = "masterDataSource")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create()
                .url("jdbc:mysql://localhost:3306/master_db")
                .username("root")
                .password("123456")
                .build();
    }
    @Bean(name = "slaveDataSource")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create()
                .url("jdbc:mysql://localhost:3306/slave_db")
                .username("root")
                .password("123456")
                .build();
    }
    @Bean
    public DynamicDataSource dynamicDataSource(
            @Qualifier("masterDataSource") DataSource masterDataSource,
            @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
        targetDataSources.put(DataSourceType.SLAVE, slaveDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        return dynamicDataSource;
    }
    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}
3.5 定义注解
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    DataSourceType value() default DataSourceType.MASTER;
}
3.6 AOP 切换数据源
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
    @Around("@annotation(dataSource)")
    public Object switchDataSource(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {
        try {
            DataSourceContextHolder.set(dataSource.value());
            return point.proceed();
        } finally {
            DataSourceContextHolder.clear();
        }
    }
}
3.7 MyBatis Mapper 示例
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper {
    @Select("SELECT id, username FROM users")
    List<User> findAll();
}
3.8 Service 层
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
    private final UserMapper userMapper;
    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
    @DataSource(DataSourceType.MASTER)
    public List<User> queryFromMaster() {
        return userMapper.findAll();
    }
    @DataSource(DataSourceType.SLAVE)
    public List<User> queryFromSlave() {
        return userMapper.findAll();
    }
}
3.9 Controller 测试
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
    private final UserService userService;
    public UserController(UserService userService) {
        this.userService = userService;
    }
    @GetMapping("/master")
    public List<User> master() {
        return userService.queryFromMaster();
    }
    @GetMapping("/slave")
    public List<User> slave() {
        return userService.queryFromSlave();
    }
}
访问 /master 和 /slave 时,会分别从不同数据库获取数据。
4、总结
🔹  通过 AbstractRoutingDataSource + ThreadLocal,可以灵活实现动态数据源切换。
🔹 可以结合 注解 + AOP 来简化调用。
🔹 常见应用场景:
- 读写分离
 - 多租户数据库
 - 分库分表路由