Spring Boot mybatis-plus 多数据源配置

MyBatis-Plus 多数据源配置详解

在日益复杂的业务场景中,单一数据源往往难以满足微服务架构下的多元化需求,例如数据库的读写分离、分库分表、以及连接不同业务模块的独立数据库等。MyBatis-Plus 作为一款广受欢迎的持久层框架,通过其强大的扩展性,为开发者提供了灵活便捷的多数据源配置方案。

本文将详细介绍两种主流的 MyBatis-Plus 多数据源配置方式:一种是官方推荐且广为使用的 dynamic-datasource-spring-boot-starter 插件,另一种是基于 AOP(面向切面编程)的手动配置方案,以帮助开发者根据项目需求做出最优选择。

推荐方案:使用 dynamic-datasource-spring-boot-starter

dynamic-datasource-spring-boot-starter 是一个由 MyBatis-Plus 团队成员开源的 Spring Boot 多数据源启动器,它提供了丰富的功能和简便的配置,是实现动态数据源切换的首选方案。

核心特性:

  • 数据源分组: 适用于读写分离、一主多从等复杂场景。
  • 多种切换方式: 支持注解、AOP以及编程方式的灵活切换。
  • 动态数据源: 支持项目启动后动态地增加或移除数据源。
  • 组件集成: 无缝集成 MyBatis-Plus、Quartz、ShardingSphere 等多种组件。
  • 分布式事务: 提供了基于 Seata 的分布式事务解决方案。
1. 引入依赖

根据您的 Spring Boot 版本,在 pom.xml 文件中引入相应的依赖:

Spring Boot 2.x:

xml 复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.2</version> <!-- 请使用最新版本 -->
</dependency>

Spring Boot 3.x:

xml 复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
    <version>4.2.0</version> <!-- 请使用最新版本 -->
</dependency>
2. 配置文件 application.yml

在配置文件中定义多个数据源,并指定主数据源。

yaml 复制代码
spring:
  datasource:
    dynamic:
      primary: master # 设置默认的数据源
      strict: false # 设置为true时,未匹配到数据源会报错,设置为false则使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/db_master?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
          username: root
          password: password123
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_1:
          url: jdbc:mysql://localhost:3306/db_slave1?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
          username: root
          password: password123
          driver-class-name: com.mysql.cj.jdbc.Driver
3. 数据源切换方式

a. 注解方式(@DS)

@DS 注解是切换数据源最便捷的方式,可以作用于类或方法上。方法上的注解优先级高于类上的注解。

java 复制代码
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @DS("slave_1") // 指定使用slave_1数据源
    @Override
    public List<User> getSlaveUsers() {
        return this.list();
    }

    @Override
    public List<User> getMasterUsers() {
        // 未使用@DS注解,将使用默认的master数据源
        return this.list();
    }
}

b. 编程方式(手动切换)

在某些复杂的业务场景下,需要在代码中动态决定使用哪个数据源,此时可以使用 DynamicDataSourceContextHolder 进行手动切换。

java 复制代码
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private ProductService productService;

    @Autowired
    private StockService stockService;

    public void createOrder(String productId, int amount) {
        // 查询商品信息,切换到slave数据源
        DynamicDataSourceContextHolder.push("slave_1");
        Product product = productService.getById(productId);
        DynamicDataSourceContextHolder.clear(); // 每次使用后必须清空

        // 扣减库存,切换到master数据源
        DynamicDataSourceContextHolder.push("master");
        stockService.deduct(productId, amount);
        DynamicDataSourceContextHolder.clear();

        // ... 创建订单等操作,使用默认数据源
    }
}

手动配置方案:基于 AOP 实现

对于希望拥有更高自由度或不愿引入额外依赖的开发者,可以采用自定义注解和 AOP 的方式手动实现动态数据源切换。其核心原理是利用 Spring 提供的 AbstractRoutingDataSource 类。

1. 添加 AOP 依赖

pom.xml 中确保已引入 AOP 相关依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 自定义数据源注解

创建一个注解,用于在需要切换数据源的方法上进行标识。

java 复制代码
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value();
}
3. 创建动态数据源上下文

使用 ThreadLocal 存储当前线程需要使用的数据源名称,以确保线程安全。

java 复制代码
public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }
}
4. 实现 AbstractRoutingDataSource

这是实现动态数据源的核心,通过重写 determineCurrentLookupKey 方法,从 DynamicDataSourceContextHolder 中获取当前数据源的 key。

java 复制代码
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}
5. 配置数据源

通过 Java Config 的方式配置多个数据源,并将它们注入到 DynamicDataSource 中。

java 复制代码
@Configuration
public class DataSourceConfig {

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

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

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

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(masterDataSource);
        return dataSource;
    }
}

并在 application.yml 中配置相应的数据源信息:

yaml 复制代码
spring:
  datasource:
    master:
      url: jdbc:mysql://localhost:3306/db_master...
      # ...
    slave:
      url: jdbc:mysql://localhost:3306/db_slave1...
      # ...
6. 编写 AOP 切面

创建切面,拦截带有 @DataSource 注解的方法,在方法执行前后设置和清除数据源 key。

java 复制代码
@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.annotation.DataSource)")
    public void dataSourcePointCut() {
    }

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource != null) {
            DynamicDataSourceContextHolder.setDataSourceKey(dataSource.value());
        }
        try {
            return point.proceed();
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceKey();
        }
    }
}

方案对比与选择

特性 dynamic-datasource-spring-boot-starter 手动配置 AOP
易用性 ,开箱即用,配置简单。 ,需要手动编写较多代码。
功能丰富度 ,支持分组、动态数据源、分布式事务等。 ,仅实现基础的切换功能。
灵活性 ,支持多种切换方式。 ,完全自定义实现,可控性强。
依赖 引入额外 starter 依赖。 无需额外依赖,更为轻量。

选择建议:

  • 对于绝大多数项目,特别是追求开发效率和稳定性的团队,强烈推荐使用 dynamic-datasource-spring-boot-starter
  • 如果项目对依赖有严格控制,或需要实现高度定制化的数据源切换逻辑,可以选择 手动配置 AOP 的方式。

注意事项

  • 事务管理: 在多数据源环境下,需要特别注意事务的一致性。对于单个数据源内的事务,Spring 的 @Transactional 依然有效。但对于跨多个数据源的分布式事务,则需要引入如 Seata 等分布式事务解决方案。
  • AOP顺序: 如果同时使用了 @Transactional@DS 注解,需要注意 AOP 的执行顺序,确保数据源切换在事务开启之前执行。

通过以上两种方案的介绍,开发者可以根据自身项目的实际需求和团队的技术栈,选择最合适的方式来配置和管理 MyBatis-Plus 的多数据源,从而构建出更加健壮和可扩展的应用系统。

相关推荐
brzhang10 小时前
为什么 OpenAI 不让 LLM 生成 UI?深度解析 OpenAI Apps SDK 背后的新一代交互范式
前端·后端·架构
EnCi Zheng10 小时前
JPA 连接 PostgreSQL 数据库完全指南
java·数据库·spring boot·后端·postgresql
brzhang10 小时前
OpenAI Apps SDK ,一个好的 App,不是让用户知道它该怎么用,而是让用户自然地知道自己在做什么。
前端·后端·架构
LucianaiB10 小时前
从玩具到工业:基于 CodeBuddy code CLI 构建电力变压器绕组短路智能诊断系统
后端
_extraordinary_11 小时前
Java SpringBoot(一)--- 下载Spring相关插件,创建一个Spring项目,创建项目出现的问题
java·spring boot·spring
ruleslol11 小时前
SpringBoot14-ThreadLocal讲解
spring boot
武子康12 小时前
大数据-118 - Flink 批处理 DataSet API 全面解析:应用场景、代码示例与优化机制
大数据·后端·flink
不要再敲了12 小时前
Spring Security 完整使用指南
java·后端·spring
IT_陈寒12 小时前
Redis 高性能缓存设计:7个核心优化策略让你的QPS提升300%
前端·人工智能·后端
摇滚侠12 小时前
【IT老齐456】Spring Boot优雅开发多线程应用,笔记01
spring boot·redis·笔记