SpringBoot進(jìn)行多數(shù)據(jù)源配置的詳細(xì)步驟
多數(shù)據(jù)源核心概念
多數(shù)據(jù)源是指在一個(gè)應(yīng)用程序中同時(shí)連接和使用多個(gè)數(shù)據(jù)庫的能力。在實(shí)際開發(fā)中,我們經(jīng)常會(huì)遇到以下場景需要多數(shù)據(jù)源:
- 同時(shí)連接生產(chǎn)數(shù)據(jù)庫和報(bào)表數(shù)據(jù)庫
- 讀寫分離場景(主庫寫,從庫讀)
- 微服務(wù)架構(gòu)中需要訪問其他服務(wù)的數(shù)據(jù)庫
- 多租戶系統(tǒng)中每個(gè)租戶有獨(dú)立數(shù)據(jù)庫
多數(shù)據(jù)源實(shí)現(xiàn)示例
多數(shù)據(jù)源的配置文件以及配置類
application.yml 配置示例
spring:
datasource:
jdbc-url: jdbc:mysql://localhost:3306/db1 # 主數(shù)據(jù)源
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
pool-name: PrimaryHikariPool
# 最大連接數(shù)
maximum-pool-size: 20
# 最小空閑連接
minimum-idle: 5
# 空閑連接超時(shí)時(shí)間(ms)
idle-timeout: 30000
# 連接最大生命周期(ms)
max-lifetime: 1800000
# 獲取連接超時(shí)時(shí)間(ms)
connection-timeout: 30000
connection-test-query: SELECT 1
second-datasource:
jdbc-url: jdbc:mysql://localhost:3306/db2 # 主數(shù)據(jù)源
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
pool-name: SecondHikariPool
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1多數(shù)據(jù)源配置類
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DbConfig {
@Bean("db1DataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSourceProperties db1DataSourceProperties() {
return new DataSourceProperties();
}
@Bean(name = "db1DataSource")
public DataSource dataSource() {
return db1DataSourceProperties().initializeDataSourceBuilder().build();
}
@Bean("db2DataSourceProperties")
@ConfigurationProperties(prefix = "spring.second-datasource")
public DataSourceProperties db2DataSourceProperties() {
return new DataSourceProperties();
}
@Bean(name = "db2DataSource")
public DataSource db2DataSource() {
return db2DataSourceProperties().initializeDataSourceBuilder().build();
}
}
禁用默認(rèn)數(shù)據(jù)源
多數(shù)據(jù)源時(shí)需在主類排除自動(dòng)配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}JPA 多數(shù)據(jù)源配置
主數(shù)據(jù)源 JAP 配置
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Objects;
@Configuration
// 啟用 Spring 的事務(wù)管理功能,允許使用 @Transactional 注解來管理事務(wù)
@EnableTransactionManagement
// 啟用 JPA 倉庫的自動(dòng)掃描和注冊(cè)功能
@EnableJpaRepositories(
// 指定要掃描的 JPA 倉庫接口所在的包路徑
basePackages = "com.example.db1",
// 指定使用的實(shí)體管理器工廠的 Bean 名稱
entityManagerFactoryRef = "db1EntityManagerFactory",
// 指定使用的事務(wù)管理器的 Bean 名稱
transactionManagerRef = "db1TransactionManager"
)
public class Db1JpaConfig {
/**
* 創(chuàng)建實(shí)體管理器工廠的 Bean,并將其標(biāo)記為主要的實(shí)體管理器工廠 Bean
*/
@Bean(name = "db1EntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
@Qualifier("db1DataSource")DataSource dataSource,
JpaProperties jpaProperties) {
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(),
new HashMap<>(), null)
// 設(shè)置數(shù)據(jù)源
.dataSource(dataSource)
// 指定要掃描的實(shí)體類所在的包路徑
.packages("com.example.db1")
// 設(shè)置持久化單元的名稱
.persistenceUnit("db1")
// 設(shè)置 JPA 的屬性
.properties(jpaProperties.getProperties())
.build();
}
/**
* 創(chuàng)建事務(wù)管理器的 Bean,并將其標(biāo)記為主要的事務(wù)管理器 Bean
*/
@Bean(name = "db1TransactionManager")
public PlatformTransactionManager transactionManager(
@Qualifier("db1EntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(Objects.requireNonNull(entityManagerFactory.getObject()));
}
/**
* QueryDSL的核心組件
*/
@Bean(name = "db1JPAQueryFactory")
public JPAQueryFactory db1JPAQueryFactory(
@Qualifier("db1EntityManagerFactory") EntityManager entityManager) {
return new JPAQueryFactory(entityManager);
}
}從數(shù)據(jù)源 JAP 集成配置(略)
MyBatis 多數(shù)據(jù)源配置
主數(shù)據(jù)源 MyBatis 配置
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
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 org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
// 此注解用于指定 MyBatis Mapper 接口的掃描范圍和對(duì)應(yīng)的 SqlSessionFactory 引用
@MapperScan(
// 指定要掃描的 Mapper 接口所在的基礎(chǔ)包路徑
basePackages = "com.example.mapper.db1",
// 配置使用的 SqlSessionFactory Bean 的名稱
sqlSessionFactoryRef = "db1SqlSessionFactory"
)
public class Db1MyBatisConfig {
/**
* 創(chuàng)建 SqlSessionFactory Bean
*/
@Bean("db1SqlSessionFactory")
public SqlSessionFactory db1SqlSessionFactory(
@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
// 創(chuàng)建 SqlSessionFactoryBean 實(shí)例,用于創(chuàng)建 SqlSessionFactory
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 設(shè)置 SqlSessionFactory 使用的數(shù)據(jù)源
sessionFactory.setDataSource(dataSource);
// 設(shè)置 Mapper XML 文件的位置,使用 PathMatchingResourcePatternResolver 來查找匹配的資源
sessionFactory.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/db1/*.xml"));
// 獲取并返回 SqlSessionFactory 實(shí)例
return sessionFactory.getObject();
}
/**
* 創(chuàng)建 SqlSessionTemplate Bean
*/
@Bean("db1SqlSessionTemplate")
public SqlSessionTemplate db1SqlSessionTemplate(
@Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
// 創(chuàng)建并返回 SqlSessionTemplate 實(shí)例,用于簡化 MyBatis 的操作
return new SqlSessionTemplate(sqlSessionFactory);
}
/**
* 創(chuàng)建事務(wù)管理器的 Bean,并將其標(biāo)記為主要的事務(wù)管理器 Bean
*/
@Bean("db1TransactionManager")
public PlatformTransactionManager transactionManager(
@Qualifier("db1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
從數(shù)據(jù)源 MyBatis 配置(略)
事務(wù)管理:跨數(shù)據(jù)源事務(wù)處理
單數(shù)據(jù)源事務(wù)
在單數(shù)據(jù)源場景下,Spring的事務(wù)管理非常簡單:
@Service
public class AccountService {
@Transactional // 使用默認(rèn)事務(wù)管理器
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// do some thing ...
}
}
多數(shù)據(jù)源事務(wù)挑戰(zhàn)
多數(shù)據(jù)源事務(wù)面臨的主要問題是分布式事務(wù)的挑戰(zhàn)。Spring 的 @Transactional 注解默認(rèn)只能管理單個(gè)事務(wù)管理器,無法直接協(xié)調(diào)多個(gè)數(shù)據(jù)源的事務(wù)。
解決方案對(duì)比:
| 方案 | 原理 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場景 |
|---|---|---|---|---|
| JTA (Java Transaction API) | 使用全局事務(wù)協(xié)調(diào)器 | 強(qiáng)一致性 | 性能開銷大,配置復(fù)雜 | 需要強(qiáng)一致性的金融系統(tǒng) |
| 最終一致性 (Saga模式) | 通過補(bǔ)償操作實(shí)現(xiàn) | 高性能,松耦合 | 實(shí)現(xiàn)復(fù)雜,需要補(bǔ)償邏輯 | 高并發(fā),可接受短暫不一致 |
| 本地消息表 | 通過消息隊(duì)列保證 | 可靠性高 | 需要額外表存儲(chǔ)消息 | 需要可靠異步處理的場景 |
事務(wù)管理器:DataSourceTransactionManager 和 JpaTransactionManager
DataSourceTransactionManager 和 JpaTransactionManager 是 Spring 框架中針對(duì)不同持久層技術(shù)的事務(wù)管理器。
技術(shù)棧適配差異
1.DataSourceTransactionManager
適用場景:純 JDBC、MyBatis、JdbcTemplate 等基于原生 SQL 的數(shù)據(jù)訪問技術(shù)
事務(wù)控制對(duì)象:直接管理 java.sql.Connection ,通過數(shù)據(jù)庫連接實(shí)現(xiàn)事務(wù)
局限性:
- 無法自動(dòng)綁定 JPA 或 Hibernate 的 EntityManager/Session 到當(dāng)前事務(wù)上下文
- 混合使用 JDBC 和 JPA 時(shí)可能導(dǎo)致連接隔離(各自使用獨(dú)立連接),破壞事務(wù)一致性
2.JpaTransactionManager
適用場景:JPA 規(guī)范實(shí)現(xiàn)(如 Hibernate、EclipseLink)
事務(wù)控制對(duì)象:管理 JPA EntityManager,通過其底層連接協(xié)調(diào)事務(wù)
核心優(yōu)勢(shì):
- 自動(dòng)將 EntityManager 綁定到線程上下文,確保同一事務(wù)中多次操作使用同一連接
- 支持 JPA 的延遲加載(Lazy Loading)、緩存同步等特性
3.混合技術(shù)棧的特殊情況
混合技術(shù)棧需嚴(yán)格隔離事務(wù)管理器,并考慮分布式事務(wù)需求
JPA操作使用JpaTransactionManager,MyBatis操作使用DataSourceTransactionManager
跨數(shù)據(jù)源事務(wù)需引入分布式事務(wù)(如Atomikos),否則不同數(shù)據(jù)源的事務(wù)無法保證原子性
若一個(gè) Service 方法同時(shí)使用 JPA和 Mybatis(未驗(yàn)證):
- 使用 DataSourceTransactionManager 可能導(dǎo)致兩個(gè)操作使用不同連接,違反 ACID
- 使用 JpaTransactionManager 能保證兩者共享同一連接(因 JPA 底層復(fù)用 DataSource 連接)
事務(wù)同步機(jī)制對(duì)比
| 特性 | DataSourceTransactionManager | JpaTransactionManager |
|---|---|---|
| 連接資源管理 | 直接管理 Connection | 通過 EntityManager 間接管理連接 |
| 跨技術(shù)兼容性 | 僅限 JDBC 系技術(shù) | 支持 JPA 及其混合場景(如 JPA+JDBC) |
| 高級(jí) ORM 功能支持 | 不支持(如延遲加載) | 完整支持 JPA 特性 |
| 配置復(fù)雜度 | 簡單(僅需 DataSource) | 需額外配置 EntityManagerFactory |
多數(shù)據(jù)源事務(wù)使用
事務(wù)配置詳見上文
多數(shù)據(jù)源事務(wù)使用示例
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService {
@Transactional(transactionManager = "db1TransactionManager") // 指定事務(wù)管理器
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// do some thing ...
}
}
基于 AbstractRoutingDataSource 的動(dòng)態(tài)數(shù)據(jù)源
動(dòng)態(tài)數(shù)據(jù)源上下文
public class DynamicDataSourceContextHolder {
// 使用ThreadLocal保證線程安全
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
// 數(shù)據(jù)源列表
public static final String PRIMARY_DS = "primary";
public static final String SECONDARY_DS = "secondary";
public static void setDataSourceType(String dsType) {
CONTEXT_HOLDER.set(dsType);
}
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
動(dòng)態(tài)數(shù)據(jù)源配置
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DynamicDataSourceConfig {
/**
* 創(chuàng)建動(dòng)態(tài)數(shù)據(jù)源 Bean,并將其設(shè)置為主要的數(shù)據(jù)源 Bean
*/
@Bean
@Primary
public DataSource dynamicDataSource(
@Qualifier("db1DataSource") DataSource db1DataSource,
@Qualifier("db2DataSource") DataSource db2DataSource) {
// 用于存儲(chǔ)目標(biāo)數(shù)據(jù)源的映射,鍵為數(shù)據(jù)源標(biāo)識(shí),值為數(shù)據(jù)源實(shí)例
Map<Object, Object> targetDataSources = new HashMap<>();
// 將主數(shù)據(jù)源添加到目標(biāo)數(shù)據(jù)源映射中,使用自定義的主數(shù)據(jù)源標(biāo)識(shí)
targetDataSources.put(DynamicDataSourceContextHolder.PRIMARY_DS, db1DataSource);
// 將從數(shù)據(jù)源添加到目標(biāo)數(shù)據(jù)源映射中,使用自定義的從數(shù)據(jù)源標(biāo)識(shí)
targetDataSources.put(DynamicDataSourceContextHolder.SECONDARY_DS, db2DataSource);
// 創(chuàng)建自定義的動(dòng)態(tài)數(shù)據(jù)源實(shí)例
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 設(shè)置動(dòng)態(tài)數(shù)據(jù)源的目標(biāo)數(shù)據(jù)源映射
dynamicDataSource.setTargetDataSources(targetDataSources);
// 設(shè)置動(dòng)態(tài)數(shù)據(jù)源的默認(rèn)目標(biāo)數(shù)據(jù)源為主數(shù)據(jù)源
dynamicDataSource.setDefaultTargetDataSource(db1DataSource);
return dynamicDataSource;
}
/**
* 自定義動(dòng)態(tài)數(shù)據(jù)源類,繼承自 AbstractRoutingDataSource
*/
private static class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 確定當(dāng)前要使用的數(shù)據(jù)源的標(biāo)識(shí)
* @return 當(dāng)前數(shù)據(jù)源的標(biāo)識(shí)
*/
@Override
protected Object determineCurrentLookupKey() {
// 從上下文持有者中獲取當(dāng)前要使用的數(shù)據(jù)源類型
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
}
基于AOP的讀寫分離實(shí)現(xiàn)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ReadOnly {
// 標(biāo)記為讀操作
}
@Aspect
@Component
public class ReadWriteDataSourceAspect {
@Before("@annotation(readOnly)")
public void beforeSwitchDataSource(JoinPoint point, ReadOnly readOnly) {
DynamicDataSourceContextHolder.setDataSourceType(DynamicDataSourceContextHolder.SECONDARY_DS);
}
@After("@annotation(readOnly)")
public void afterSwitchDataSource(JoinPoint point, ReadOnly readOnly) {
DynamicDataSourceContextHolder.clearDataSourceType();
}
}使用示例
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Transactional
public void createProduct(Product product) {
// 默認(rèn)使用主數(shù)據(jù)源(寫)
productRepository.save(product);
}
@ReadOnly // 執(zhí)行該注解標(biāo)記的方法時(shí),前后都會(huì)執(zhí)行ReadWriteDataSourceAspect切面類方法
@Transactional
public Product getProduct(Long id) {
// 使用從數(shù)據(jù)源(讀)
return productRepository.findById(id).orElse(null);
}
@ReadOnly
@Transactional
public List<Product> listProducts() {
// 使用從數(shù)據(jù)源(讀)
return productRepository.findAll();
}
}常見問題與解決方案
典型問題排查表
| 方案 | 原理 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場景 |
|---|---|---|---|---|
| JTA (Java Transaction API) | 使用全局事務(wù)協(xié)調(diào)器 | 強(qiáng)一致性 | 性能開銷大,配置復(fù)雜 | 需要強(qiáng)一致性的金融系統(tǒng) |
| 最終一致性 (Saga模式) | 通過補(bǔ)償操作實(shí)現(xiàn) | 高性能,松耦合 | 實(shí)現(xiàn)復(fù)雜,需要補(bǔ)償邏輯 | 高并發(fā),可接受短暫不一致 |
| 本地消息表 | 通過消息隊(duì)列保證 | 可靠性高 | 需要額外表存儲(chǔ)消息 | 需要可靠異步處理的場景 |
數(shù)據(jù)源切換失敗案例分析
問題描述:
在動(dòng)態(tài)數(shù)據(jù)源切換場景下,有時(shí)切換不生效,仍然使用默認(rèn)數(shù)據(jù)源。
原因分析:
- 數(shù)據(jù)源切換代碼被異常繞過,未執(zhí)行
- 線程池場景下線程復(fù)用導(dǎo)致上下文污染
- AOP 順序問題導(dǎo)致切換時(shí)機(jī)不對(duì)
解決方案:
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // 確保最先執(zhí)行
public class DataSourceAspect {
@Around("@annotation(targetDataSource)")
public Object around(ProceedingJoinPoint joinPoint, TargetDataSource targetDataSource) throws Throwable {
String oldKey = DynamicDataSourceContextHolder.getDataSourceType();
try {
DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
return joinPoint.proceed();
} finally {
// 恢復(fù)為原來的數(shù)據(jù)源
if (oldKey != null) {
DynamicDataSourceContextHolder.setDataSourceType(oldKey);
} else {
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}
}
???????// 線程池配置確保清理上下文
@Configuration
public class ThreadPoolConfig {
@Bean
public ExecutorService asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("Async-");
executor.setTaskDecorator(runnable -> {
String dsKey = DynamicDataSourceContextHolder.getDataSourceType();
return () -> {
try {
if (dsKey != null) {
DynamicDataSourceContextHolder.setDataSourceType(dsKey);
}
runnable.run();
} finally {
DynamicDataSourceContextHolder.clearDataSourceType();
}
};
});
executor.initialize();
return executor.getThreadPoolExecutor();
}
}多數(shù)據(jù)源與緩存集成
當(dāng)多數(shù)據(jù)源與緩存(如 Redis)一起使用時(shí),需要注意緩存鍵的設(shè)計(jì):
@Service
public class CachedUserService {
@Autowired
private PrimaryUserRepository primaryUserRepository;
@Autowired
private SecondaryUserRepository secondaryUserRepository;
@Autowired
private RedisTemplate<String, User> redisTemplate;
private String getCacheKey(String source, Long userId) {
return String.format("user:%s:%d", source, userId);
}
@Cacheable(value = "users", key = "#root.target.getCacheKey('primary', #userId)")
public User getPrimaryUser(Long userId) {
return primaryUserRepository.findById(userId).orElse(null);
}
@Cacheable(value = "users", key = "#root.target.getCacheKey('secondary', #userId)")
public User getSecondaryUser(Long userId) {
return secondaryUserRepository.findById(userId).orElse(null);
}
@CacheEvict(value = "users", allEntries = true)
public void clearAllUserCache() {
// 清除所有用戶緩存
}
}
總結(jié)與擴(kuò)展
技術(shù)選型建議
| 場景 | 推薦方案 | 理由 |
|---|---|---|
| 簡單多數(shù)據(jù)源,無交叉訪問 | 獨(dú)立配置多個(gè)數(shù)據(jù)源 | 簡單直接,易于維護(hù) |
| 需要?jiǎng)討B(tài)切換數(shù)據(jù)源 | AbstractRoutingDataSource | 靈活,可運(yùn)行時(shí)決定數(shù)據(jù)源 |
| 需要強(qiáng)一致性事務(wù) | JTA(XA) | 保證ACID,但性能較低 |
| 高并發(fā),最終一致性可接受 | Saga模式 | 高性能,松耦合 |
| 讀寫分離 | AOP+注解方式 | 透明化,對(duì)業(yè)務(wù)代碼侵入小 |
以上就是SpringBoot進(jìn)行多數(shù)據(jù)源配置的詳細(xì)步驟的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot多數(shù)據(jù)源配置的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot項(xiàng)目中多數(shù)據(jù)源配置方法與使用場景
- SpringBoot多數(shù)據(jù)源配置完整指南
- Mybatis+Druid+MybatisPlus多數(shù)據(jù)源配置方法
- Mybatis-plus配置多數(shù)據(jù)源,連接多數(shù)據(jù)庫方式
- SpringBoot中配置多數(shù)據(jù)源的方法詳解
- springboot配置多數(shù)據(jù)源(靜態(tài)和動(dòng)態(tài)數(shù)據(jù)源)
- SpringBoot+mybatis-plus實(shí)現(xiàn)多數(shù)據(jù)源配置的詳細(xì)步驟
相關(guān)文章
如何讓@EnableConfigurationProperties的值注入到@Value中
這篇文章主要介紹了如何讓@EnableConfigurationProperties的值注入到@Value中的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-06-06
Spring?Security權(quán)限注解啟動(dòng)及邏輯處理使用示例
這篇文章主要為大家介紹了Spring?Security權(quán)限注解啟動(dòng)及邏輯處理使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
Feign如何使用protobuf的類作為參數(shù)調(diào)用
這篇文章主要介紹了Feign如何使用protobuf的類作為參數(shù)調(diào)用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
指定springboot的jar運(yùn)行內(nèi)存方式
這篇文章主要介紹了指定springboot的jar運(yùn)行內(nèi)存方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
Java實(shí)現(xiàn)視頻格式轉(zhuǎn)化的操作代碼
在當(dāng)今數(shù)字化時(shí)代,視頻已成為我們?nèi)粘I詈凸ぷ髦胁豢苫蛉钡囊徊糠?不同的設(shè)備和平臺(tái)可能支持不同的視頻格式,因此,視頻格式轉(zhuǎn)換的需求也日益增長,本文將介紹如何使用Java實(shí)現(xiàn)視頻格式轉(zhuǎn)換,需要的朋友可以參考下2025-01-01

