Java多數(shù)據(jù)源的三種實現(xiàn)方式小結
1、什么時候會用到多數(shù)據(jù)源(Multiple Data Sources)
在Java開發(fā)中,“多數(shù)據(jù)源”指的是在一個應用程序中配置和使用多個不同的數(shù)據(jù)庫連接。通常情況下,一個Java應用程序會連接到一個單一的數(shù)據(jù)庫。然而,在一些復雜的應用場景中,可能需要訪問多個不同的數(shù)據(jù)庫,這時就需要配置多數(shù)據(jù)源。
1.1、多數(shù)據(jù)源具體含義
- 多個數(shù)據(jù)庫實例:多數(shù)據(jù)源可以是指同一個類型(如MySQL)的多個數(shù)據(jù)庫實例。例如,一個應用程序可能需要同時連接到兩個不同的MySQL數(shù)據(jù)庫,一個用于存儲用戶信息,另一個用于存儲訂單信息。
- 不同類型的數(shù)據(jù)庫:多數(shù)據(jù)源也可以是指不同類型的數(shù)據(jù)庫。例如,一個應用程序可能需要連接一個MySQL數(shù)據(jù)庫來存儲用戶信息,同時還需要連接一個Oracle數(shù)據(jù)庫來存儲財務數(shù)據(jù)。
- 不同的訪問策略:多數(shù)據(jù)源配置也可能用于實現(xiàn)數(shù)據(jù)庫訪問策略的不同,比如讀寫分離(一個數(shù)據(jù)源用于寫操作,另一個用于讀操作),或者是在多租戶架構下,不同租戶使用不同的數(shù)據(jù)源。
1.2、為什么需要多數(shù)據(jù)源
- 業(yè)務需求:不同的業(yè)務模塊可能需要存儲在不同的數(shù)據(jù)庫中。例如,財務數(shù)據(jù)和用戶數(shù)據(jù)可能分別存儲在兩個獨立的數(shù)據(jù)庫中,以便更好地進行管理和安全控制。
- 系統(tǒng)架構:在大型分布式系統(tǒng)中,通常會使用分庫分表來應對海量數(shù)據(jù)的挑戰(zhàn),不同的數(shù)據(jù)庫實例可能分布在不同的服務器上。
- 讀寫分離:為了提高系統(tǒng)的性能,尤其是在高并發(fā)場景下,常見的做法是將數(shù)據(jù)庫的讀操作和寫操作分開,讀操作可以從多個只讀的從庫(Slave)中獲取數(shù)據(jù),而寫操作則寫入到主庫(Master)。
- 遷移或兼容性:在系統(tǒng)遷移或升級的過程中,可能需要同時訪問新舊兩個數(shù)據(jù)庫系統(tǒng),或者需要兼容不同版本的數(shù)據(jù)庫。
- 多租戶支持:在SaaS應用中,每個租戶的數(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è)務邏輯動態(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ù)據(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è)務層,我們可以通過注解設置當前數(shù)據(jù)源的標識符來實現(xiàn)數(shù)據(jù)源的動態(tài)切換。
@Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface WR { String value() default "W"; }
在業(yè)務層,我們可以通過注解設置當前數(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ù)據(jù)源
在業(yè)務層具體使用方法上增加注解,實現(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()); // 可以手動指定主庫對應的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()); // 可以手動指定主庫對應的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è)務層具體使用
在應用程序中,具體的 DAO 層使用 SqlSessionFactory 和 SqlSessionTemplate 時,MyBatis 會根據(jù) @MapperScan 指定的包路徑,使用相應的數(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ù)項目需要調整版本 --> </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 # 設置為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è)務中對多數(shù)據(jù)源的需求。
3 總結
這三種實現(xiàn)多數(shù)據(jù)源的方式各有優(yōu)缺點,適用于不同的場景,具體選擇哪種方式還需根據(jù)實際需求來判斷。此外,配置多數(shù)據(jù)源的方式不止于此,例如在使用 Spring Data JPA 時,也可以通過配置多個 EntityManager 實現(xiàn)多數(shù)據(jù)源。每個 EntityManager 可以與特定的 DataSource 關聯(lián),從而實現(xiàn)對多個數(shù)據(jù)庫的操作。因此,在多數(shù)據(jù)源的配置中,靈活選擇適合的方案至關重要。
源碼地址:https://github.com/arkhamYJ/datasource.git
到此這篇關于Java多數(shù)據(jù)源的三種實現(xiàn)方式小結的文章就介紹到這了,更多相關Java多數(shù)據(jù)源內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java服務調用失敗報Service com.oneinfinite.adflow.api.service.T
在Java開發(fā)中,服務調用是常見的操作,尤其是在微服務架構中,然而,服務調用過程中可能會遇到各種問題,下面我們來看看如何解決Service com.oneinfinite.adflow.api.service.TestService with version 0.0.0 not found的問題吧2025-03-03SpringBoot中項目如何讀取外置logback配置文件
這篇文章主要介紹了SpringBoot中項目如何讀取外置logback配置文件問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11Java 8中Stream API的這些奇技淫巧!你Get了嗎?
這篇文章主要介紹了Java 8中Stream API的這些奇技淫巧!你Get了嗎?文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-08-08Java創(chuàng)建可執(zhí)行JAR文件的多種方式
本文主要介紹了Java創(chuàng)建可執(zhí)行JAR文件的多種方式,使用JDK的jar工具、IDE、Maven和Gradle來創(chuàng)建和配置可執(zhí)行JAR文件,具有一定的參考價值,感興趣的可以了解一下2024-07-07