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 来简化调用。
🔹 常见应用场景:
- 读写分离
- 多租户数据库
- 分库分表路由