SpringBoot自定義動(dòng)態(tài)數(shù)據(jù)源的流程步驟
1. 原理
動(dòng)態(tài)數(shù)據(jù)源,本質(zhì)上是把多個(gè)數(shù)據(jù)源存儲(chǔ)在一個(gè) Map 中,當(dāng)需要使用某一個(gè)數(shù)據(jù)源時(shí),使用 key 獲取指定數(shù)據(jù)源進(jìn)行處理。而在 Spring 中已提供了抽象類 AbstractRoutingDataSource 來實(shí)現(xiàn)此功能,繼承 AbstractRoutingDataSource 類并覆寫其 determineCurrentLookupKey() 方法監(jiān)聽獲取 key 即可,該方法只需要返回?cái)?shù)據(jù)源 key 即可,也就是存放數(shù)據(jù)源的 Map 的 key。
因此,我們?cè)趯?shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源的,只需要繼承它,實(shí)現(xiàn)自己的獲取數(shù)據(jù)源邏輯即可。AbstractRoutingDataSource 頂級(jí)繼承了 DataSource,所以它也是可以做為數(shù)據(jù)源對(duì)象,因此項(xiàng)目中使用它作為主數(shù)據(jù)源。
1.1. AbstractRoutingDataSource 源碼解析

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
// 目標(biāo)數(shù)據(jù)源 map 集合,存儲(chǔ)將要切換的多數(shù)據(jù)源 bean 信息,可以通過 setTargetDataSource(Map<Object, Object> mp) 設(shè)置
@Nullable
private Map<Object, Object> targetDataSources;
// 未指定數(shù)據(jù)源時(shí)的默認(rèn)數(shù)據(jù)源對(duì)象,可以通過 setDefaultTargetDataSouce(Object obj) 設(shè)置
@Nullable
private Object defaultTargetDataSource;
...
// 數(shù)據(jù)源查找接口,通過該接口的 getDataSource(String dataSourceName) 獲取數(shù)據(jù)源信息
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
//解析 targetDataSources 之后的 DataSource 的 map 集合
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource;
//將 targetDataSources 的內(nèi)容轉(zhuǎn)化一下放到 resolvedDataSources 中,將 defaultTargetDataSource 轉(zhuǎn)為 DataSource 賦值給 resolvedDefaultDataSource
public void afterPropertiesSet() {
//如果目標(biāo)數(shù)據(jù)源為空,會(huì)拋出異常,在系統(tǒng)配置時(shí)應(yīng)至少傳入一個(gè)數(shù)據(jù)源
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
} else {
//初始化 resolvedDataSources 的大小
this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
//遍歷目標(biāo)數(shù)據(jù)源信息 map 集合,對(duì)其中的 key,value 進(jìn)行解析
this.targetDataSources.forEach((key, value) -> {
// resolveSpecifiedLookupKey 方法沒有做任何處理,只是將 key 繼續(xù)返回
Object lookupKey = this.resolveSpecifiedLookupKey(key);
// 將目標(biāo)數(shù)據(jù)源 map 集合中的 value 值(Druid 數(shù)據(jù)源信息)轉(zhuǎn)為 DataSource 類型
DataSource dataSource = this.resolveSpecifiedDataSource(value);
// 將解析之后的 key,value 放入 resolvedDataSources 集合中
this.resolvedDataSources.put(lookupKey, dataSource);
});
if (this.defaultTargetDataSource != null) {
// 將默認(rèn)目標(biāo)數(shù)據(jù)源信息解析并賦值給 resolvedDefaultDataSource
this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
}
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
}
protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) {
return (DataSource)dataSource;
} else if (dataSource instanceof String) {
return this.dataSourceLookup.getDataSource((String)dataSource);
} else {
throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
}
// 因?yàn)?AbstractRoutingDataSource 繼承 AbstractDataSource,而 AbstractDataSource 實(shí)現(xiàn)了 DataSource 接口,所有存在獲取數(shù)據(jù)源連接的方法
public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return this.determineTargetDataSource().getConnection(username, password);
}
// 最重要的一個(gè)方法,也是 DynamicDataSource 需要實(shí)現(xiàn)的方法
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
// 調(diào)用實(shí)現(xiàn)類中重寫的 determineCurrentLookupKey 方法拿到當(dāng)前線程要使用的數(shù)據(jù)源的名稱
Object lookupKey = this.determineCurrentLookupKey();
// 去解析之后的數(shù)據(jù)源信息集合中查詢?cè)摂?shù)據(jù)源是否存在,如果沒有拿到則使用默認(rèn)數(shù)據(jù)源 resolvedDefaultDataSource
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
}
@Nullable
protected abstract Object determineCurrentLookupKey();
}
1.2. 關(guān)鍵類說明
忽略掉 controller/service/entity/mapper/xml介紹。
application.yml:數(shù)據(jù)源配置文件。但是如果數(shù)據(jù)源比較多的話,根據(jù)實(shí)際使用,最佳的配置方式還是獨(dú)立配置比較好。DynamicDataSourceRegister:動(dòng)態(tài)數(shù)據(jù)源注冊(cè)配置文件DynamicDataSource:動(dòng)態(tài)數(shù)據(jù)源配置類,繼承自AbstractRoutingDataSourceTargetDataSource:動(dòng)態(tài)數(shù)據(jù)源注解,切換當(dāng)前線程的數(shù)據(jù)源DynamicDataSourceAspect:動(dòng)態(tài)數(shù)據(jù)源設(shè)置切面,環(huán)繞通知,切換當(dāng)前線程數(shù)據(jù)源,方法注解優(yōu)先DynamicDataSourceContextHolder:動(dòng)態(tài)數(shù)據(jù)源上下文管理器,保存當(dāng)前數(shù)據(jù)源的key,默認(rèn)數(shù)據(jù)源名,所有數(shù)據(jù)源key
1.3. 開發(fā)流程
- 添加配置文件,設(shè)置默認(rèn)數(shù)據(jù)源配置,和其他數(shù)據(jù)源配置
- 編寫
DynamicDataSource類,繼承AbstractRoutingDataSource類,并實(shí)現(xiàn)determineCurrentLookupKey()方法 - 編寫
DynamicDataSourceHolder上下文管理類,管理當(dāng)前線程的使用的數(shù)據(jù)源,及所有數(shù)據(jù)源的key; - 編寫
DynamicDataSourceRegister類通過讀取配置文件動(dòng)態(tài)注冊(cè)多數(shù)據(jù)源,并在啟動(dòng)類上導(dǎo)入(@Import)該類 - 自定義數(shù)據(jù)源切換注解
TargetDataSource,并實(shí)現(xiàn)相應(yīng)的切面,環(huán)繞通知切換當(dāng)前線程數(shù)據(jù)源,注解優(yōu)先級(jí)(DynamicDataSourceHolder.setDynamicDataSourceKey()>Method>Class)
2. 實(shí)現(xiàn)
2.1. 引入 Maven 依賴
<!-- web 模塊依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- spring 核心 aop 模塊依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- Druid 數(shù)據(jù)源連接池依賴 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> <!-- mybatis 依賴 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!-- mysql驅(qū)動(dòng) --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.24</version> </dependency> <!-- lombok 模塊依賴 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-text</artifactId> <version>1.10.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
2.2. application.yml 配置文件
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding-utf8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
custom:
datasource:
names: ds1,ds2
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/content_center?useUnicode
username: root
password: root
ds2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/trade?useUnicode
username: root
password: root
2.3. 創(chuàng)建 DynamicDataSource 繼承 AbstractRoutingDataSource 類
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
/**
* @Description: 繼承Spring AbstractRoutingDataSource 實(shí)現(xiàn)路由切換
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 決定當(dāng)前線程使用哪種數(shù)據(jù)源
* @return 數(shù)據(jù)源 key
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
2.4. 編寫 DynamicDataSourceHolder 類,管理 DynamicDataSource 上下文
import java.util.ArrayList;
import java.util.List;
/**
* @Description: 動(dòng)態(tài)數(shù)據(jù)源上下文管理
*/
public class DynamicDataSourceHolder {
// 存放當(dāng)前線程使用的數(shù)據(jù)源類型信息
private static final ThreadLocal<String> DYNAMIC_DATASOURCE_KEY = new ThreadLocal<String>();
// 存放數(shù)據(jù)源 key
private static final List<String> DATASOURCE_KEYS = new ArrayList<String>();
// 默認(rèn)數(shù)據(jù)源 key
public static final String DEFAULT_DATESOURCE_KEY = "master";
//設(shè)置數(shù)據(jù)源
public static void setDynamicDataSourceType(String key) {
DYNAMIC_DATASOURCE_KEY.set(key);
}
//獲取數(shù)據(jù)源
public static String getDynamicDataSourceType() {
return DYNAMIC_DATASOURCE_KEY.get();
}
//清除數(shù)據(jù)源
public static void removeDynamicDataSourceType() {
DYNAMIC_DATASOURCE_KEY.remove();
}
public static void addDataSourceKey(String key) {
DATASOURCE_KEYS.add(key)
}
/**
* 判斷指定 key 當(dāng)前是否存在
*
* @param key
* @return boolean
*/
public static boolean containsDataSource(String key){
return DATASOURCE_KEYS.contains(key);
}
}
2.5. 編寫 DynamicDataSourceRegister 讀取配置文件注冊(cè)多數(shù)據(jù)源
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import java.util.Objects;
/**
* @Description: 注冊(cè)動(dòng)態(tài)數(shù)據(jù)源
* 初始化數(shù)據(jù)源和提供了執(zhí)行動(dòng)態(tài)切換數(shù)據(jù)源的工具類
* EnvironmentAware(獲取配置文件配置的屬性值)
*/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
// 指定默認(rèn)數(shù)據(jù)源類型 (springboot2.0 默認(rèn)數(shù)據(jù)源是 hikari 如何想使用其他數(shù)據(jù)源可以自己配置)
// private static final String DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";
private static final String DEFAULT_DATASOURCE_TYPE = "com.alibaba.druid.pool.DruidDataSource";
// 默認(rèn)數(shù)據(jù)源
private DataSource defaultDataSource;
// 用戶自定義數(shù)據(jù)源
private Map<String, DataSource> customDataSources = new HashMap<>();
/**
* 加載多數(shù)據(jù)源配置
* @param env 當(dāng)前環(huán)境
*/
@Override
public void setEnvironment(Environment env) {
initDefaultDataSource(env);
initCustomDataSources(env);
}
/**
* 初始化主數(shù)據(jù)源
* @param env
*/
private void initDefaultDataSource(Environment env) {
// 讀取主數(shù)據(jù)源
Map<String, Object> dsMap = new HashMap<>();
dsMap.put("type", env.getProperty("spring.datasource.type", DEFAULT_DATASOURCE_TYPE));
dsMap.put("driver", env.getProperty("spring.datasource.driver-class-name"));
dsMap.put("url", env.getProperty("spring.datasource.url"));
dsMap.put("username", env.getProperty("spring.datasource.username"));
dsMap.put("password", env.getProperty("spring.datasource.password"));
defaultDataSource = buildDataSource(dsMap);
}
/**
* 初始化更多數(shù)據(jù)源
* @param env
*/
private void initCustomDataSources(Environment env) {
// 讀取配置文件獲取更多數(shù)據(jù)源
String dsPrefixs = env.getProperty("custom.datasource.names");
if (!StringUtils.isBlank(dsPrefixs)) {
for (String dsPrefix : dsPrefixs.split(",")) {
dsPrefix = fsPrefix.trim()
if (!StringUtils.isBlank(dsPrefix)) {
Map<String, Object> dsMap = new HashMap<>();
dsMap.put("type", env.getProperty("custom.datasource." + dsPrefix + ".type", DEFAULT_DATASOURCE_TYPE));
dsMap.put("driver", env.getProperty("custom.datasource." + dsPrefix + ".driver-class-name"));
dsMap.put("url", env.getProperty("custom.datasource." + dsPrefix + ".url"));
dsMap.put("username", env.getProperty("custom.datasource." + dsPrefix + ".username"));
dsMap.put("password", env.getProperty("custom.datasource." + dsPrefix + ".password"));
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
}
}
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
// 將主數(shù)據(jù)源添加到更多數(shù)據(jù)源中
targetDataSources.put(DynamicDataSourceHolder.DEFAULT_DATASOURCE_KEY, defaultDataSource);
DynamicDataSourceHolder.addDataSourceKey(DynamicDataSourceHolder.DEFAULT_DATASOURCE_KEY);
// 添加更多數(shù)據(jù)源
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
DynamicDataSourceContextHolder.addDataSourceKey(key);
}
// 創(chuàng)建 DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition); // 注冊(cè)到 Spring 容器中
LOGGER.info("Dynamic DataSource Registry");
}
/**
* 創(chuàng)建 DataSource
* @param dsMap 數(shù)據(jù)庫配置參數(shù)
* @return DataSource
*/
public DataSource buildDataSource(Map<String, Object> dsMap) {
try {
Object type = dsMap.get("type");
if (type == null)
type = DEFAULT_DATASOURCE_TYPE;// 默認(rèn)DataSource
Class<? extends DataSource> dataSourceType = (Class<? extends DataSource>)Class.forName((String)type);
String driverClassName = String.valueOf(dsMap.get("driver"));
String url = String.valueOf(dsMap.get("url"));
String username = String.valueOf(dsMap.get("username"));
String password = String.valueOf(dsMap.get("password"));
// 自定義 DataSource 配置
DataSourceBuilder<? extends DataSource> factory = DataSourceBuilder.create()
.driverClassName(driverClassName)
.url(url)
.username(username)
.password(password)
.type(dataSourceType);
return factory.build();
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2.6. 在啟動(dòng)器類上添加 @Import,導(dǎo)入 register 類
// 注冊(cè)動(dòng)態(tài)多數(shù)據(jù)源
@Import({ DynamicDataSourceRegister.class })
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2.7. 自定義注解 @TargetDataSource
/**
* 自定義多數(shù)據(jù)源切換注解
* 優(yōu)先級(jí):DynamicDataSourceHolder.setDynamicDataSourceKey() > Method > Class
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切換數(shù)據(jù)源名稱
*/
public String value() default DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY;
}
2.8. 定義切面攔截 @TargetDataSource
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
@Aspect
// 保證在 @Transactional 等注解前面執(zhí)行
@Order(-1)
@Component
public class DataSourceAspect {
// 設(shè)置 DataSource 注解的切點(diǎn)表達(dá)式
@Pointcut("@annotation(com.ayi.config.datasource.DynamicDataSource)")
public void dynamicDataSourcePointCut(){
}
//環(huán)繞通知
@Around("dynamicDataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
String key = getDefineAnnotation(joinPoint).value();
if (!DynamicDataSourceHolder.containsDataSource(key)) {
LOGGER.error("數(shù)據(jù)源[{}]不存在,使用默認(rèn)數(shù)據(jù)源[{}]", key, DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY)
key = DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY;
}
DynamicDataSourceHolder.setDynamicDataSourceKey(key);
try {
return joinPoint.proceed();
} finally {
DynamicDataSourceHolder.removeDynamicDataSourceKey();
}
}
/**
* 先判斷方法的注解,后判斷類的注解,以方法的注解為準(zhǔn)
* @param joinPoint 切點(diǎn)
* @return TargetDataSource
*/
private TargetDataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
TargetDataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(TargetDataSource.class);
if (Objects.nonNull(methodSignature)) {
return dataSourceAnnotation;
} else {
Class<?> dsClass = joinPoint.getTarget().getClass();
return dsClass.getAnnotation(TargetDataSource.class);
}
}
}
以上就是SpringBoot自定義動(dòng)態(tài)數(shù)據(jù)源的流程步驟的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot動(dòng)態(tài)數(shù)據(jù)源的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot中動(dòng)態(tài)數(shù)據(jù)源配置與使用詳解
- SpringBoot配置動(dòng)態(tài)數(shù)據(jù)源的實(shí)戰(zhàn)詳解
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的項(xiàng)目實(shí)踐
- SpringBoot動(dòng)態(tài)數(shù)據(jù)源連接測(cè)試的操作詳解
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的方法總結(jié)
- springboot配置多數(shù)據(jù)源(靜態(tài)和動(dòng)態(tài)數(shù)據(jù)源)
- SpringBoot中動(dòng)態(tài)數(shù)據(jù)源是實(shí)現(xiàn)與用途
- springboot 動(dòng)態(tài)數(shù)據(jù)源的實(shí)現(xiàn)方法(Mybatis+Druid)
- springboot動(dòng)態(tài)數(shù)據(jù)源+分布式事務(wù)的實(shí)現(xiàn)
相關(guān)文章
Mybatis分頁的實(shí)現(xiàn)及使用注解開發(fā)操作
這篇文章主要介紹了Mybatis分頁的實(shí)現(xiàn)及使用注解開發(fā)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
java本機(jī)內(nèi)存分配Native?memory?allocation?mmap失敗問題解決
這篇文章主要介紹了java本機(jī)內(nèi)存分配Native?memory?allocation?mmap失敗問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
關(guān)于@Transactional事務(wù)嵌套使用方式
Spring框架通過@Transactional注解來管理事務(wù),它可以作用于類和方法上,用于聲明事務(wù)的屬性,如傳播行為、隔離級(jí)別、超時(shí)時(shí)間等,Spring事務(wù)是基于AOP實(shí)現(xiàn)的,它在運(yùn)行時(shí)為加了@Transactional注解的方法或類創(chuàng)建代理2024-11-11
rabbitmq basicReject/basicNack/basicRecover的區(qū)別及說明
這篇文章主要介紹了rabbitmq basicReject/basicNack/basicRecover的區(qū)別及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01

