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_dbusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverslave: # 從數據源url: jdbc:mysql://localhost:3306/slave_dbusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
3.使用 @DS 注解切換數據源
在Service類或方法上使用 @DS(“數據源名稱”) 指定數據源
@Service
@DS("master") // 類級別默認數據源
public class UserServiceImpl implements UserService {@Autowiredprivate 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注解)
@DSTransactionalpublic 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_dbusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcedruid:initial-size: 5max-active: 20slave:url: jdbc:mysql://localhost:3306/slave_dbusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcedruid:initial-size: 3max-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 {@Overrideprotected 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@Primarypublic 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;}// 配置事務管理器@Beanpublic 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 {@Autowiredprivate 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 {// 主庫事務管理器@Beanpublic PlatformTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}// 從庫事務管理器@Beanpublic 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() {// 操作多個數據源
}