首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring Boot + MyBatis 多数据源实战:AOP 切面自动路由实现读写分离与多数据源切换

     編輯:Java 綜合教學

架構設計:註解 + AOP + ThreadLocal + AbstractRoutingDataSource

核心組件:

  1. @TargetDataSource 註解:標記方法/類使用哪個數據源。
  2. DynamicDataSourceContextHolder:ThreadLocal 存儲當前線程數據源 Key。
  3. DynamicRoutingDataSource extends AbstractRoutingDataSource:根據 Key 路由到實際 DataSource。
  4. DataSourceAspect:AOP 切面,方法執行前切換 Key,執行後清理。

核心代碼實現

1. 數據源枚舉與 ThreadLocal 持有者

public enum DataSourceKey { MASTER, SLAVE_1, SLAVE_2 }

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

    public static void set(DataSourceKey key) { CONTEXT.set(key); }
    public static DataSourceKey get() { return CONTEXT.get(); }
    public static void clear() { CONTEXT.remove(); }
}

2. 路由 DataSource

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

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

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

    @Primary
    @Bean
    public DataSource routingDataSource() {
        DynamicRoutingDataSource ds = new DynamicRoutingDataSource();
        Map map = new HashMap<>();
        map.put(DataSourceKey.MASTER, masterDataSource());
        map.put(DataSourceKey.SLAVE_1, slave1DataSource());
        map.put(DataSourceKey.SLAVE_2, slave2DataSource());
        ds.setTargetDataSources(map);
        ds.setDefaultTargetDataSource(masterDataSource()); // 默認主庫
        return ds;
    }

    public static class DynamicRoutingDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceContextHolder.get();
        }
    }
}

3. 切換註解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    DataSourceKey value() default DataSourceKey.MASTER;
}

4. AOP 切面(關鍵優化:優先用 @Before,避免 @Around 複雜度)

@Aspect @Component @Order(-100)  // 高優先級,早於事務切面
public class DataSourceAspect {
    @Pointcut("@annotation(com.example.annotation.TargetDataSource)")
    public void dsPointcut() {}

    @Before("dsPointcut()")
    public void switchDataSource(JoinPoint point) {
        TargetDataSource ann = getAnnotation(point);
        if (ann != null) {
            DynamicDataSourceContextHolder.set(ann.value());
            log.debug("切換數據源 -> {}", ann.value());
        }
    }

    @After("dsPointcut()")
    public void restoreDataSource() {
        DynamicDataSourceContextHolder.clear();
        log.debug("清理數據源上下文");
    }

    private TargetDataSource getAnnotation(JoinPoint point) {
        MethodSignature sig = (MethodSignature) point.getSignature();
        Method method = sig.getMethod();
        // 方法級註解優先
        TargetDataSource ann = method.getAnnotation(TargetDataSource.class);
        if (ann != null) return ann;
        // 類級註解兜底
        return method.getDeclaringClass().getAnnotation(TargetDataSource.class);
    }
}

使用示例

@Service @RequiredArgsConstructor
public class OrderService {
    private final OrderMapper orderMapper;
    private final UserMapper userMapper;

    @TargetDataSource(DataSourceKey.MASTER)  // 寫操作走主庫
    @Transactional
    public Long createOrder(OrderDTO dto) {
        Order order = new Order(dto);
        orderMapper.insert(order);
        return order.getId();
    }

    @TargetDataSource(DataSourceKey.SLAVE_1)  // 讀操作走從庫
    public OrderDetail getOrderDetail(Long id) {
        Order order = orderMapper.selectById(id);
        User user = userMapper.selectById(order.getUserId()); // 同一切面生效
        return new OrderDetail(order, user);
    }
}

關鍵優化點總結

優化說明
@Before + @After 替代 @Around無需手動 proceed()、異常傳播更自然、代碼更簡潔
@Pointcut 復用表達式定義一次,多通知復用,其他切面亦可引用
@Order(-100) 早於 @Transactional確保數據源切換在事務開啟**之前**完成,避免事務綁定錯誤連接
ThreadLocal 清理在 @After無論正常/異常返回,線程上下文必定清理,防止線程池污染
方法級 > 類級註解優先級靈活覆蓋,細粒度控制

進階擴展

  • 讀寫分離自動路由:自定義註解 @ReadOnly,切面自動輪詢從庫(Round Robin / 隨機 / 加權)。
  • 分布式事務:Seata AT 模式需關閉自動提交、代理 DataSource,注意與路由 DataSource 共存配置。
  • 多租戶:租戶 ID 解析 → 查找租戶專屬 DataSource → 切換上下文,邏輯同構。

配置文件範例

spring:
  datasource:
    master:
      url: jdbc:mysql://master:3306/app?useSSL=false
      username: root
      password: pwd
      hikari:
        maximum-pool-size: 20
    slave1:
      url: jdbc:mysql://slave1:3306/app?useSSL=false
      username: readonly
      password: pwd
    slave2:
      url: jdbc:mysql://slave2:3306/app?useSSL=false
      username: readonly
      password: pwd
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved