springboot配置多個(gè)數(shù)據(jù)源兩種方式實(shí)現(xiàn)
在我們的實(shí)際業(yè)務(wù)中可能會(huì)遇到;在一個(gè)項(xiàng)目里面讀取多個(gè)數(shù)據(jù)庫(kù)的數(shù)據(jù)來(lái)進(jìn)行展示,spring對(duì)同時(shí)配置多個(gè)數(shù)據(jù)源是支持的。
本文中將展示兩種方法來(lái)實(shí)現(xiàn)這個(gè)功能。
springboot+mybatis
第一種方式:
在配置文件中配置多個(gè)數(shù)據(jù)源,然后通過(guò)配置類(lèi)來(lái)獲取數(shù)據(jù)源以及mapper相關(guān)的掃描配置
pom.xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> </parent> <dependencies> <!-- druid數(shù)據(jù)源驅(qū)動(dòng) --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--mybatis SpringBoot依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!-- aop依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency> <!-- 通用mapper --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>1.1.5</version> </dependency> <!-- druid監(jiān)控依賴 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.28</version> </dependency> </dependencies>
application.yml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
url2: jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=utf8
username2: root
password2: 123456DruidDBConfig 連接池相關(guān)配置
package com.xbz.common.config;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Druid監(jiān)控
*/
@SuppressWarnings("AlibabaRemoveCommentedCode")
@Configuration
public class DruidDBConfig {
private Logger logger = LoggerFactory.getLogger(DruidDBConfig.class);
/**
* 注冊(cè)ServletRegistrationBean
* @return
*/
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*");
reg.addInitParameter("allow", ""); //白名單
return reg;
}
/**
* 注冊(cè)FilterRegistrationBean
* @return
*/
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
filterRegistrationBean.addInitParameter("profileEnable", "true");
filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
filterRegistrationBean.addInitParameter("DruidWebStatFilter", "/*");
return filterRegistrationBean;
}
}MasterDbConfig 注意讀取數(shù)據(jù)庫(kù)連接相關(guān)的鍵,以及掃描實(shí)體、mapper等
package com.xbz.common.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@MapperScan(basePackages = MasterDbConfig.PACKAGE , sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDbConfig {
private Logger logger = LoggerFactory.getLogger(MasterDbConfig.class);
// 精確到 master 目錄,以便跟其他數(shù)據(jù)源隔離
static final String PACKAGE = "com.xbz.**.dao.master";
private static final String MAPPER_LOCATION = "classpath*:mapper/master/*.xml";
private static final String DOMAIN_PACKAGE = "com.xbz.**.domain";
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driverClassName}")
private String driverClassName;
@Bean(name="masterDataSource") //聲明其為Bean實(shí)例
@Primary //在同樣的DataSource中,首先使用被標(biāo)注的DataSource
public DataSource masterDataSource() {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(this.dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
return datasource;
}
@Bean(name = "masterTransactionManager")
@Primary
public DataSourceTransactionManager masterTransactionManager() {
return new DataSourceTransactionManager(masterDataSource());
}
@Bean(name = "masterSqlSessionFactory")
@Primary
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource masterDataSource)
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(masterDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(MasterDbConfig.MAPPER_LOCATION));
sessionFactory.setTypeAliasesPackage(DOMAIN_PACKAGE);
//mybatis 數(shù)據(jù)庫(kù)字段與實(shí)體類(lèi)屬性駝峰映射配置
sessionFactory.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return sessionFactory.getObject();
}
}ClusterDbConfig
package com.xbz.common.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.sql.SQLException;
/**
* 從數(shù)據(jù)源配置
* 若需要配置更多數(shù)據(jù)源 , 直接在yml中添加數(shù)據(jù)源配置再增加相應(yīng)的新的數(shù)據(jù)源配置類(lèi)即可
*/
@Configuration
@MapperScan(basePackages = ClusterDbConfig.PACKAGE , sqlSessionFactoryRef = "clusterSqlSessionFactory")
public class ClusterDbConfig {
private Logger logger = LoggerFactory.getLogger(ClusterDbConfig.class);
// 精確到 cluster 目錄,以便跟其他數(shù)據(jù)源隔離
static final String PACKAGE = "com.xbz.**.dao.cluster";
private static final String MAPPER_LOCATION = "classpath*:mapper/cluster/*.xml";
private static final String DOMAIN_PACKAGE = "com.xbz.**.domain";
@Value("${spring.datasource.url2}")
private String dbUrl;
@Value("${spring.datasource.username2}")
private String username;
@Value("${spring.datasource.password2}")
private String password;
@Value("${spring.datasource.driverClassName}")
private String driverClassName;
@Bean(name="clusterDataSource") //聲明其為Bean實(shí)例
public DataSource clusterDataSource() {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(this.dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
return datasource;
}
@Bean(name = "clusterTransactionManager")
public DataSourceTransactionManager clusterTransactionManager() {
return new DataSourceTransactionManager(clusterDataSource());
}
@Bean(name = "clusterSqlSessionFactory")
public SqlSessionFactory clusterSqlSessionFactory(@Qualifier("clusterDataSource") DataSource culsterDataSource)
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(culsterDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(ClusterDbConfig.MAPPER_LOCATION));
sessionFactory.setTypeAliasesPackage(DOMAIN_PACKAGE);
//mybatis 數(shù)據(jù)庫(kù)字段與實(shí)體類(lèi)屬性駝峰映射配置
sessionFactory.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return sessionFactory.getObject();
}
}
不同的數(shù)據(jù)源配置不佟的mapper掃描位置,然后需要哪一個(gè)數(shù)據(jù)源就注入哪一個(gè)mapper接口即可


這樣獲取的數(shù)據(jù)就是來(lái)自不同的數(shù)據(jù)源了,這種方法比較簡(jiǎn)單。
方法二
配置一個(gè)默認(rèn)使用的數(shù)據(jù)源,然后定義多個(gè)其他的數(shù)據(jù)源,使用aop形成注解式選擇數(shù)據(jù)源
# 默認(rèn)數(shù)據(jù)源
spring:
datasource:
druid:
# 數(shù)據(jù)庫(kù)訪問(wèn)配置, 使用druid數(shù)據(jù)源
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
# 其他數(shù)據(jù)源
custom:
datasource:
druid:
type: com.alibaba.druid.pool.DruidDataSource
names: fishlog,fishgame
fishlog:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
fishgame:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&characterEncoding=utf8
username: root
password: 123456然后啟動(dòng)類(lèi)中注入注解

注解類(lèi)及參數(shù)value TargetDataSource
package com.sysmg.common.config.datasoure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}DynamicDataSource
package com.sysmg.common.config.datasoure;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}DynamicDataSourceAspect切面實(shí)現(xiàn)
package com.sysmg.common.config.datasoure;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Order(-10) // 保證該AOP在@Transactional之前執(zhí)行
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(targetDataSource)")
public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Throwable {
String dsId = targetDataSource.value();
if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
System.err.println("數(shù)據(jù)源[{}]不存在,使用默認(rèn)數(shù)據(jù)源 > {}" + targetDataSource.value() + point.getSignature());
} else {
System.out.println("UseDataSource : {} > {}" + targetDataSource.value() + point.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
}
}
@After("@annotation(targetDataSource)")
public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
System.out.println("RevertDataSource : {} > {}"+targetDataSource.value()+point.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
}
}DynamicDataSourceContextHolder
package com.sysmg.common.config.datasoure;
import java.util.ArrayList;
import java.util.List;
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static List<String> dataSourceIds = new ArrayList<String>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
public static boolean containsDataSource(String dataSourceId){
return dataSourceIds.contains(dataSourceId);
}
}DynamicDataSourceRegister數(shù)據(jù)源注冊(cè)類(lèi)
package com.sysmg.common.config.datasoure;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
@Override
public void setEnvironment(Environment environment) {
System.out.println("DynamicDataSourceRegister.setEnvironment()");
initDefaultDataSource(environment);
initCustomDataSources(environment);
}
private void initDefaultDataSource(Environment env) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.druid.");
Map<String, Object> dsMap = new HashMap<String, Object>();
dsMap.put("type", propertyResolver.getProperty("type"));
dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));
dsMap.put("url", propertyResolver.getProperty("url"));
dsMap.put("username", propertyResolver.getProperty("username"));
dsMap.put("password", propertyResolver.getProperty("password"));
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource, env);
}
private void initCustomDataSources(Environment env) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.druid.");
String dsPrefixs = propertyResolver.getProperty("names");
for (String dsPrefix : dsPrefixs.split(",")) {// 多個(gè)數(shù)據(jù)源
Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
dataBinder(ds, env);
}
}
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map<String, Object> dsMap) {
Object type = dsMap.get("type");
if (type == null) {
type = DATASOURCE_TYPE_DEFAULT;// 默認(rèn)DataSource
}
Class<? extends DataSource> dataSourceType;
try {
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
String driverClassName = dsMap.get("driverClassName").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
.username(username).password(password).type(dataSourceType);
return factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
private void dataBinder(DataSource dataSource, Environment env) {
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
dataBinder.setConversionService(conversionService);
dataBinder.setIgnoreNestedProperties(false);// false
dataBinder.setIgnoreInvalidFields(false);// false
dataBinder.setIgnoreUnknownFields(true);// true
if (dataSourcePropertyValues == null) {
Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
Map<String, Object> values = new HashMap<>(rpr);
values.remove("type");
values.remove("driverClassName");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("DynamicDataSourceRegister.registerBeanDefinitions()");
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put("dataSource", defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
DynamicDataSourceContextHolder.dataSourceIds.add(key);
}
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);
}
}![]()
這里使用的時(shí)候把需要使用的表名注入,如果是默認(rèn)的數(shù)據(jù)源,則不需要加這個(gè)注解,具體的springboot自定義注解的建立,可參考http://www.dhdzp.com/article/239952.htm,這里的mapper文件和實(shí)體掃描可以跟默認(rèn)的數(shù)據(jù)源配置相同即可
到此這篇關(guān)于springboot配置多個(gè)數(shù)據(jù)源兩種方式實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)springboot配置多個(gè)數(shù)據(jù)源內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot多數(shù)據(jù)源配置并通過(guò)注解實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源
- SpringBoot多數(shù)據(jù)源讀寫(xiě)分離的自定義配置問(wèn)題及解決方法
- 使用SpringBoot配置多數(shù)據(jù)源的經(jīng)驗(yàn)分享
- spring注解 @PropertySource配置數(shù)據(jù)源全流程
- Spring配置數(shù)據(jù)源的三種方式(小結(jié))
- 解決springboot項(xiàng)目不配置數(shù)據(jù)源啟動(dòng)報(bào)錯(cuò)問(wèn)題
- springboot整合多數(shù)據(jù)源配置方式
- Spring配置數(shù)據(jù)源流程與作用詳解
相關(guān)文章
Java8實(shí)現(xiàn)FTP及SFTP文件上傳下載
這篇文章主要介紹了Java8實(shí)現(xiàn)FTP及SFTP文件上傳下載,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
使用google.kaptcha來(lái)生成圖片驗(yàn)證碼的實(shí)現(xiàn)方法
這篇文章主要介紹了使用google.kaptcha來(lái)生成圖片驗(yàn)證碼的實(shí)現(xiàn)方法,非常不錯(cuò)具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-09-09
Java多線程Runable售票系統(tǒng)實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了Java多線程Runable售票系統(tǒng)實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
一文搞懂Java頂層類(lèi)之Object類(lèi)的使用
java.lang.Object類(lèi)是Java語(yǔ)言中的根類(lèi),即所有類(lèi)的父類(lèi)。它中描述的所有方法子類(lèi)都可以使用。本文主要介紹了Object類(lèi)中toString和equals方法的使用,感興趣的小伙伴可以了解一下2022-11-11
Spring boot工具類(lèi)靜態(tài)屬性注入及多環(huán)境配置詳解
這篇文章主要為大家詳細(xì)介紹了Spring boot工具類(lèi)靜態(tài)屬性注入,及多環(huán)境配置詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04
換了最新的idea如何將原來(lái)舊版本的idea設(shè)置導(dǎo)進(jìn)新的idea中
這篇文章主要介紹了換了最新的idea如何將原來(lái)舊版本的idea設(shè)置導(dǎo)進(jìn)新的idea中,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
Spring需要三個(gè)級(jí)別緩存解決循環(huán)依賴原理解析
這篇文章主要為大家介紹了Spring需要三個(gè)級(jí)別緩存解決循環(huán)依賴原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02

