Java多數(shù)據(jù)源的三種實現(xiàn)方式小結(jié)
1、什么時候會用到多數(shù)據(jù)源(Multiple Data Sources)
在Java開發(fā)中,“多數(shù)據(jù)源”指的是在一個應(yīng)用程序中配置和使用多個不同的數(shù)據(jù)庫連接。通常情況下,一個Java應(yīng)用程序會連接到一個單一的數(shù)據(jù)庫。然而,在一些復(fù)雜的應(yīng)用場景中,可能需要訪問多個不同的數(shù)據(jù)庫,這時就需要配置多數(shù)據(jù)源。
1.1、多數(shù)據(jù)源具體含義
- 多個數(shù)據(jù)庫實例:多數(shù)據(jù)源可以是指同一個類型(如MySQL)的多個數(shù)據(jù)庫實例。例如,一個應(yīng)用程序可能需要同時連接到兩個不同的MySQL數(shù)據(jù)庫,一個用于存儲用戶信息,另一個用于存儲訂單信息。
- 不同類型的數(shù)據(jù)庫:多數(shù)據(jù)源也可以是指不同類型的數(shù)據(jù)庫。例如,一個應(yīng)用程序可能需要連接一個MySQL數(shù)據(jù)庫來存儲用戶信息,同時還需要連接一個Oracle數(shù)據(jù)庫來存儲財務(wù)數(shù)據(jù)。
- 不同的訪問策略:多數(shù)據(jù)源配置也可能用于實現(xiàn)數(shù)據(jù)庫訪問策略的不同,比如讀寫分離(一個數(shù)據(jù)源用于寫操作,另一個用于讀操作),或者是在多租戶架構(gòu)下,不同租戶使用不同的數(shù)據(jù)源。
1.2、為什么需要多數(shù)據(jù)源
- 業(yè)務(wù)需求:不同的業(yè)務(wù)模塊可能需要存儲在不同的數(shù)據(jù)庫中。例如,財務(wù)數(shù)據(jù)和用戶數(shù)據(jù)可能分別存儲在兩個獨立的數(shù)據(jù)庫中,以便更好地進行管理和安全控制。
- 系統(tǒng)架構(gòu):在大型分布式系統(tǒng)中,通常會使用分庫分表來應(yīng)對海量數(shù)據(jù)的挑戰(zhàn),不同的數(shù)據(jù)庫實例可能分布在不同的服務(wù)器上。
- 讀寫分離:為了提高系統(tǒng)的性能,尤其是在高并發(fā)場景下,常見的做法是將數(shù)據(jù)庫的讀操作和寫操作分開,讀操作可以從多個只讀的從庫(Slave)中獲取數(shù)據(jù),而寫操作則寫入到主庫(Master)。
- 遷移或兼容性:在系統(tǒng)遷移或升級的過程中,可能需要同時訪問新舊兩個數(shù)據(jù)庫系統(tǒng),或者需要兼容不同版本的數(shù)據(jù)庫。
- 多租戶支持:在SaaS應(yīng)用中,每個租戶的數(shù)據(jù)可能被隔離存儲在不同的數(shù)據(jù)庫中,以確保數(shù)據(jù)的獨立性和安全性。
2、Java中實現(xiàn)多數(shù)據(jù)源的三種方式
2.1 基于Spring提供的AbstractRoutingDataSource
使用 Spring 提供的 AbstractRoutingDataSource 實現(xiàn)多數(shù)據(jù)源配置是一種動態(tài)數(shù)據(jù)源管理的有效方法。通過繼承 AbstractRoutingDataSource,可以根據(jù)某些條件(如請求上下文、當前線程信息等)動態(tài)地選擇要使用的數(shù)據(jù)源。這種方式非常適合需要根據(jù)業(yè)務(wù)邏輯動態(tài)切換數(shù)據(jù)源的場景,如讀寫分離、分庫分表等。
基于 AbstractRoutingDataSource 實現(xiàn)多數(shù)據(jù)源配置的詳細步驟如下:
2.1.1 創(chuàng)建一個 DynamicDataSource 類
首先,創(chuàng)建一個繼承 AbstractRoutingDataSource 的類,來實現(xiàn)數(shù)據(jù)源的動態(tài)切換邏輯。
@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {
// 當前使用的數(shù)據(jù)源標識
public static ThreadLocal<String> name=new ThreadLocal<>();
// 寫數(shù)據(jù)源
@Autowired
DataSource dataSource1;
// 讀數(shù)據(jù)源
@Autowired
DataSource dataSource2;
@Override
protected Object determineCurrentLookupKey() {
return name.get();
}
@Override
public void afterPropertiesSet() {
// 為targetDataSources初始化所有數(shù)據(jù)源
Map<Object, Object> targetDataSources=new HashMap<>();
targetDataSources.put("W",dataSource1);
targetDataSources.put("R",dataSource2);
super.setTargetDataSources(targetDataSources);
// 為defaultTargetDataSource 設(shè)置默認的數(shù)據(jù)源
super.setDefaultTargetDataSource(dataSource1);
super.afterPropertiesSet();
}
}
2.1.2 配置數(shù)據(jù)源
接下來,我們需要配置多個數(shù)據(jù)源。
@Configuration
public class DataSourceConfig {
/**
數(shù)據(jù)庫1的配置
/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource1")
public DataSource dataSource1() {
// 底層會自動拿到spring.datasource中的配置, 創(chuàng)建一個DruidDataSource
return DruidDataSourceBuilder.create().build();
}
/**
數(shù)據(jù)庫2的配置
/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource2")
public DataSource dataSource2() {
// 底層會自動拿到spring.datasource中的配置, 創(chuàng)建一個DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean
public DataSourceTransactionManager transactionManager1(DynamicDataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
@Bean
public DataSourceTransactionManager transactionManager2(DynamicDataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
2.1.3 自定義注解,便于區(qū)別不同數(shù)據(jù)源
在業(yè)務(wù)層,我們可以通過注解設(shè)置當前數(shù)據(jù)源的標識符來實現(xiàn)數(shù)據(jù)源的動態(tài)切換。
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface WR {
String value() default "W";
}
在業(yè)務(wù)層,我們可以通過注解設(shè)置當前數(shù)據(jù)源的標識符來實現(xiàn)數(shù)據(jù)源的動態(tài)切換。將自己實現(xiàn)的DynamicDataSource注冊成為默認的DataSource實例后,只需要在每次使用DataSource時,提前改變一下其中的name標識,就可以快速切換數(shù)據(jù)源。
@Component
@Aspect
public class DynamicDataSourceAspect implements Ordered {
// 前置
@Before("within(org.arkham.dynamic_datasource.service.impl.*) && @annotation(wr)")
public void before(JoinPoint point, WR wr){
String name = wr.value();
DynamicDataSource.name.set(name);
}
@Override
public int getOrder() {
return 0;
}
}
2.1.4 Service通過注解設(shè)置數(shù)據(jù)源
在業(yè)務(wù)層具體使用方法上增加注解,實現(xiàn)數(shù)據(jù)源切換
@Service
public class UserImplService implements UserService {
@Autowired
UserMapper userMapper;
@Override
@WR("R") // 庫1
public List<User> list() {
return userMapper.list();
}
@Override
@WR("W") // 庫2
public void save(User friend) {
userMapper.save(friend);
}
}
2.2 使用MyBatis注冊多個SqlSessionFactory
在Spring 環(huán)境中,如果有多個數(shù)據(jù)源并且需要為每個數(shù)據(jù)源配置單獨的 SqlSessionFactory 和SqlSessionTemplate,可以通過配置多個 SqlSessionFactory 實現(xiàn)這一點。如果使用MyBatis框架,要注冊多個數(shù)據(jù)源的話,就需要將MyBatis底層的DataSource、SqlSessionFactory、DataSourceTransactionManager這些核心對象一并進行手動注冊
基于 MyBatis 注冊多個 SqlSessionFactory實現(xiàn)多數(shù)據(jù)源配置的詳細步驟如下:
2.2.1 不同數(shù)據(jù)源注冊
數(shù)據(jù)源1:
@Configuration
// 繼承mybatis:
// 1. 指定掃描的mapper接口包(主庫)
// 2. 指定使用sqlSessionFactory是哪個(主庫)
@MapperScan(basePackages = "org.arkham.dynamic_datasource_mybatis.mapper.w",
sqlSessionFactoryRef="wSqlSessionFactory")
public class WMyBatisConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource1")
public DataSource dataSource1() {
// 底層會自動拿到spring.datasource中的配置, 創(chuàng)建一個DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public SqlSessionFactory wSqlSessionFactory()
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 指定主庫
sessionFactory.setDataSource(dataSource1());
// 可以手動指定主庫對應(yīng)的mapper.xml文件
/*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/order/*.xml"));*/
return sessionFactory.getObject();
}
@Bean
@Primary
public DataSourceTransactionManager wTransactionManager(){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource1());
return dataSourceTransactionManager;
}
@Bean
public TransactionTemplate wTransactionTemplate(){
return new TransactionTemplate(wTransactionManager());
}
}
數(shù)據(jù)源2:
@Configuration
// 繼承mybatis:
// 1. 指定掃描的mapper接口包(主庫)
// 2. 指定使用sqlSessionFactory是哪個(主庫)
@MapperScan(basePackages = "org.arkham.dynamic_datasource_mybatis.mapper.r",
sqlSessionFactoryRef="rSqlSessionFactory")
public class RMyBatisConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource2")
public DataSource dataSource2() {
// 底層會自動拿到spring.datasource中的配置, 創(chuàng)建一個DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public SqlSessionFactory rSqlSessionFactory()
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 指定主庫
sessionFactory.setDataSource(dataSource2());
// 可以手動指定主庫對應(yīng)的mapper.xml文件
/*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/r/*.xml"));*/
return sessionFactory.getObject();
}
@Bean
public DataSourceTransactionManager rTransactionManager(){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource2());
return dataSourceTransactionManager;
}
@Bean
public TransactionTemplate rTransactionTemplate(){
return new TransactionTemplate(rTransactionManager());
}
2.2.2 業(yè)務(wù)層具體使用
在應(yīng)用程序中,具體的 DAO 層使用 SqlSessionFactory 和 SqlSessionTemplate 時,MyBatis 會根據(jù) @MapperScan 指定的包路徑,使用相應(yīng)的數(shù)據(jù)源。
@Service
public class UserImplService implements UserService {
@Autowired
private RUserMapper rFriendMapper;
@Autowired
private WUserMapper wFriendMapper;
// 讀-- 讀庫
@Override
public List<User> list() {
return rFriendMapper.list();
}
// 保存-- 寫庫
@Override
public void save(User user) {
wFriendMapper.save(user);
}
// 保存-- 寫庫
@Override
public void saveW(User user) {
user.setName("xman11");
wFriendMapper.save(user);
}
// 保存-- 讀庫
@Override
public void saveR(User user) {
user.setName("xman");
rFriendMapper.save(user);
}
}
2.3 使用dynamic-datasource框架
使用 dynamic-datasource-spring-boot-starter 框架可以非常方便地實現(xiàn) Java 項目中的多數(shù)據(jù)源配置。這個框架能夠支持多種數(shù)據(jù)源的自動切換、動態(tài)數(shù)據(jù)源的配置管理等功能。
基于 dynamic-datasource-spring-boot-starter 框架實現(xiàn)多數(shù)據(jù)源配置的詳細步驟如下:
2.3.1 添加依賴
首先,需要在 pom.xml 文件中添加 dynamic-datasource-spring-boot-starter 的依賴。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version> <!-- 請根據(jù)項目需要調(diào)整版本 -->
</dependency>
2.3.2 配置數(shù)據(jù)源
在application.yml 文件中配置多個數(shù)據(jù)源??梢越o每個數(shù)據(jù)源起一個唯一的名稱(例如 master 和 slave)
spring:
datasource:
dynamic:
primary: master # 默認數(shù)據(jù)源或數(shù)據(jù)源組,當未指定數(shù)據(jù)源時會使用該數(shù)據(jù)源
strict: false # 設(shè)置為true表示檢查配置文件中數(shù)據(jù)源是否有效
datasource:
master: # 數(shù)據(jù)源一
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/master_db
username: master_user
password: master_pass
slave: # 數(shù)據(jù)源二
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/slave_db
username: slave_user
password: slave_pass
2.3.3 配置數(shù)據(jù)源
使用 @DS 注解來指定某個方法或類使用特定的數(shù)據(jù)源。
@Service
public class UserImplService implements UserService {
@Autowired
UserMapper userMapper;
@Override
@DS("master")
public void save(User user) {
userMapper.save(user);
}
@Override
@DS("slave_1") // 從庫, 如果按照下劃線命名方式配置多個 , 可以指定前綴即可(組名)
public List<User> list() {
return userMapper.list();
}
}
對于 MyBatis,dynamic-datasource 同樣適用。只需正常配置 MyBatis,并在 Mapper 方法上使用 @DS 注解即可。
import com.baomidou.dynamic.datasource.annotation.DS;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@DS("master")
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserFromMaster(int id);
@DS("slave")
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserFromSlave(int id);
}
dynamic-datasource 框架提供了一種簡潔、高效的方式來實現(xiàn)多數(shù)據(jù)源的配置和動態(tài)切換。通過注解方式,可以輕松切換數(shù)據(jù)源,滿足業(yè)務(wù)中對多數(shù)據(jù)源的需求。
3 總結(jié)
這三種實現(xiàn)多數(shù)據(jù)源的方式各有優(yōu)缺點,適用于不同的場景,具體選擇哪種方式還需根據(jù)實際需求來判斷。此外,配置多數(shù)據(jù)源的方式不止于此,例如在使用 Spring Data JPA 時,也可以通過配置多個 EntityManager 實現(xiàn)多數(shù)據(jù)源。每個 EntityManager 可以與特定的 DataSource 關(guān)聯(lián),從而實現(xiàn)對多個數(shù)據(jù)庫的操作。因此,在多數(shù)據(jù)源的配置中,靈活選擇適合的方案至關(guān)重要。
源碼地址:https://github.com/arkhamYJ/datasource.git
到此這篇關(guān)于Java多數(shù)據(jù)源的三種實現(xiàn)方式小結(jié)的文章就介紹到這了,更多相關(guān)Java多數(shù)據(jù)源內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何解決報錯:java.net.BindException:無法指定被請求的地址問題
在Linux虛擬機上安裝并啟動Tomcat時遇到啟動失敗的問題,通過檢查端口及配置文件未發(fā)現(xiàn)異常,后發(fā)現(xiàn)/etc/hosts文件中缺少localhost的映射,添加后重啟Tomcat成功,Tomcat啟動時會檢查localhost的IP映射,缺失或錯誤都可能導(dǎo)致啟動失敗2024-10-10
關(guān)于Spring 中 StringUtils.isEmpty 被棄用如何正確使用
SpringBoot/SpringFramework升級后,StringUtils.isEmpty()方法已被棄用,推薦使用hasLength和hasText替代,hasLength判斷是否有長度,hasText判斷是否包含至少一個非空白字符,本文給大家介紹關(guān)于Spring 中 StringUtils.isEmpty 被棄用如何正確使用,感興趣的朋友一起看看吧2026-01-01

