MyBatis-Plus vs AbstractRoutingDataSource

MyBatis-Plus多数据源配

1.添加依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>

2.配置数据源信息

spring:
  datasource:
    dynamic:
      primary: master  # 设置默认数据源
      datasource:
        master:  # 主数据源
          url: jdbc:mysql://localhost:3306/master_db
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave:  # 从数据源
          url: jdbc:mysql://localhost:3306/slave_db
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver

3.使用 @DS 注解切换数据源
在Service类或方法上使用 @DS(“数据源名称”) 指定数据源

@Service
@DS("master")  // 类级别默认数据源
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    @DS("slave")  // 方法级别覆盖类级别数据源
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }
}

4.排除原生自动配置(可选)
如果出现冲突,在启动类排除原生数据源自动配置:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

5.多数据源的事务一致性(@DSTransactional注解)

@DSTransactional
 public void test() {
 	//db1数据源
     DeAnOrganization deAnOrganization = new DeAnOrganization();
     deAnOrganization.setId(1L);
     deAnOrganizationMapper.insert(deAnOrganization);

		//db2数据源	
     JdfLightBasic jdfLightBasic = new JdfLightBasic();
     jdfLightBasic.setOrgId("1");
     jdfLightBasicMapper.insert(jdfLightBasic);
	
	//抛出异常,@DSTransactional可保证事务唯一性,事务可正常回滚
    throw new RuntimeException("test");
 }

AbstractRoutingDataSource

使用 AbstractRoutingDataSource 配置多数据源是 Spring 原生的动态数据源解决方案,适合需要深度定制的场景。
1.依赖仅需引入jdbc+连接池

<!-- 仅需 Spring JDBC + 连接池(使用druid) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.8</version>
</dependency>

2.数据源配置

spring:
  datasource:
    master:
      url: jdbc:mysql://localhost:3306/master_db
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
      druid:
        initial-size: 5
        max-active: 20

    slave:
      url: jdbc:mysql://localhost:3306/slave_db
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
      druid:
        initial-size: 3
        max-active: 15

3.创建数据源上下文持有类

public class DataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();

    public static void setDataSource(String dsName) {
        CONTEXT.set(dsName);
    }

    public static String getDataSource() {
        return CONTEXT.get();
    }

    public static void clear() {
        CONTEXT.remove();
    }
}

4.实现动态数据源路由

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

5.配置数据源Bean

@Configuration
@MapperScan(basePackages = "com.example.mapper")
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource dynamicDataSource(
            @Qualifier("masterDataSource") DataSource master,
            @Qualifier("slaveDataSource") DataSource slave) {
        
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", master);
        targetDataSources.put("slave", slave);

        DynamicDataSource ds = new DynamicDataSource();
        ds.setDefaultTargetDataSource(master); // 默认数据源
        ds.setTargetDataSources(targetDataSources);
        ds.afterPropertiesSet();
        return ds;
    }

    // 配置事务管理器
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}

6.自定义注解实现切换

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
    String value() default "master";
}

// 切面实现
@Aspect
@Component
public class DSAspect {
    @Before("@annotation(ds)")
    public void beforeSwitchDS(JoinPoint point, DS ds) {
        DataSourceContextHolder.setDataSource(ds.value());
    }

    @After("@annotation(ds)")
    public void afterSwitchDS(JoinPoint point, DS ds) {
        DataSourceContextHolder.clear();
    }
}

7.使用示例

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @DS("master") // 使用主库
    public void addUser(User user) {
        userMapper.insert(user);
    }

    @DS("slave") // 使用从库
    public User getUser(Long id) {
        return userMapper.selectById(id);
    }
}

8.事务管理器配置

@Configuration
public class TransactionConfig {

    // 主库事务管理器
    @Bean
    public PlatformTransactionManager masterTransactionManager(
            @Qualifier("masterDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    // 从库事务管理器
    @Bean
    public PlatformTransactionManager slaveTransactionManager(
            @Qualifier("slaveDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
// 自定义数据源切换注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
    String value() default "master";
}

// 数据源切换切面(需优先于事务切面执行)
@Aspect
@Component
@Order(0) // 优先级高于 @Transactional 的切面
public class DSAspect {
    
    @Before("@annotation(ds)")
    public void beforeSwitchDS(JoinPoint joinPoint, DS ds) {
        DataSourceContextHolder.setDataSource(ds.value());
    }

    @After("@annotation(ds)")
    public void afterSwitchDS(JoinPoint joinPoint, DS ds) {
        DataSourceContextHolder.clear();
    }
}
@Service
public class BusinessService {

    // 显式指定事务管理器
    @DS("master")
    @Transactional(transactionManager = "masterTransactionManager")
    public void createOrder(Order order) {
        // 主库操作
    }

    @DS("slave")
    @Transactional(transactionManager = "slaveTransactionManager")
    public Order queryOrder(Long id) {
        // 从库查询
    }

    // 跨数据源事务(需要分布式事务支持)
    @DS("master")
    @Transactional(transactionManager = "masterTransactionManager")
    public void crossDataSourceOperation() {
        // 主库操作
        slaveOperation(); // 需要新事务
    }

    @DS("slave")
    @Transactional(transactionManager = "slaveTransactionManager", 
                  propagation = Propagation.REQUIRES_NEW)
    public void slaveOperation() {
        // 从库操作(独立事务)
    }
}

关键注意事项

AOP 执行顺序
必须确保数据源切换切面(@DS)先于事务切面执行:
// 启动类配置
@SpringBootApplication
@EnableTransactionManagement(order = Ordered.LOWEST_PRECEDENCE) // 事务切面最后执行
public class Application { ... }

事务传播机制
默认 Propagation.REQUIRED 会继承当前事务的数据源
使用 Propagation.REQUIRES_NEW 可强制开启新事务并切换数据源

分布式事务支持
如需跨数据源事务原子性,需集成 Seata 等分布式事务框架:
@DS("master")
@GlobalTransactional // Seata 全局事务注解
public void distributedTransaction() {
    // 操作多个数据源
}
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐