全站資源開放下載,感謝廣大網友的支持
鏈接失效請移步職業司平臺
非盈利平臺

非盈利平臺

只為分享一些優質內容

Java幫幫-微信公眾號

Java幫幫-微信公眾號

將分享做到極致

微信小程序

微信小程序

更方便的閱讀

職業司微信公眾號

職業司微信公眾號

實時動態通知

安卓APP

安卓APP

我們從此不分開

程序員生活志-公眾號

程序員生活志-公眾號

程序員生活學習圈,互聯網八卦黑料

支付寶贊助-Java幫幫社區
微信贊助-Java幫幫社區

spring學習:多數據源深入解析,包括使用場景【云圖智聯】

3
發表時間:2020-07-23 17:08

1 多數據源的典型使用場景

在實際開發中,經常可能遇到在一個應用中可能需要訪問多個數據庫的情況。以下是兩種典型場景:

1 業務復雜

數據分布在不同的數據庫中,筆者見過一個相對比較復雜的業務,一個業務中同時操作了9個庫。

2 讀寫分離

一些規模較小的公司,沒有專門的中間件團隊搭建讀寫分離基礎設施,因此需要業務開發人員自行實現讀寫分離。

這里的架構與上圖類似。不同的是,在讀寫分離中,主庫和從庫的數據庫是一致的(不考慮主從延遲)。數據更新操作(insert、update、delete)都是在主庫上進行,主庫將數據變更信息同步給從庫。在查詢時,可以在從庫上進行,從而分擔主庫的壓力。

2 如何實現多數據源

對于大多數的java應用,都使用了spring框架,spring-jdbc模塊提供了AbstractRoutingDataSource,其內部可以包含了多個DataSource,然后在運行時來動態的訪問哪個數據庫。這種方式訪問數據庫的架構圖如下所示:

應用直接操作的是AbstractRoutingDataSource的實現類,告訴AbstractRoutingDataSource訪問哪個數據庫,然后由AbstractRoutingDataSource從事先配置好的數據源(ds1、ds2)選擇一個,來訪問對應的數據庫。

關于如何利用AbstractRoutingDataSource實現多數據源訪問,各種博客已經很多,基本功能都能實現,但是易用性不夠好,要修改大量代碼,業務侵入性太強。

這也是筆者為什么寫這篇文章的原因,這里提供了一種更加簡單易用的多數據源實現,筆者稱之為RoutingDataSource。在讀者對sprign-aop不是很了解的情況下,也能非常容易上手。而且筆者將這個組件發布到了maven中央倉庫,因此你可以直接pom.xml中進行引用。

  1. <dependency>

  2.     <groupId>io.github.tianshouzhi</groupId>

  3.     <artifactId>routing-datasource</artifactId>

  4.     <version>1.0.0</version>

  5. </dependency>

3 RoutingDataSource的配置

假設我們有2個庫,db1(包含user表),db2(包含user_account表)。我們需要配置2個數據源(類型任意:druid、c3p0、dbcp、tomcat-jdbc任何實現JDBC規范的數據源都可以),將其交給RoutingDataSource進行管理,相關配置如下:

  1. <!--配置ds1,訪問數據庫db1-->

  2. <bean id="ds1" class="org.apache.ibatis.datasource.pooled.PooledDataSource">

  3.     <property name="username" value="root"/>

  4.     <property name="password" value="your password"/>

  5.     <property name="url" value="jdbc:mysql://localhost:3306/db1"/>

  6.     <property name="driver" value="com.mysql.jdbc.Driver"/>

  7. </bean>

  8. <!--配置ds2,訪問數據庫db2-->

  9. <bean id="ds2" class="org.apache.ibatis.datasource.pooled.PooledDataSource">

  10.     <property name="username" value="root"/>

  11.     <property name="password" value="your password"/>

  12.     <property name="url" value="jdbc:mysql://localhost:3306/db2"/>

  13.     <property name="driver" value="com.mysql.jdbc.Driver"/>

  14. </bean>

  15. <!--配置RoutingDataSource,其管理了ds1和ds2-->

  16. <bean id="routingDataSource" class="io.github.tianshouzhi.routing.RoutingDataSource">

  17.     <property name="targetDataSources">

  18.         <map>

  19.             <!--entry的key,將在稍后將看到的@Routing注解中使用到-->

  20.             <entry key="ds1" value-ref="ds1"/>

  21.             <entry key="ds2" value-ref="ds2"/>

  22.         </map>

  23.     </property>

  24.     <!--配置默認數據源,在RoutingDataSource無法確定使用哪個數據源時,將會使用默認的數據源-->

  25.     <property name="defaultTargetDataSource" value="ds1"/>

  26. </bean>

在實際開發中,我們通常不會直接操作數據源,而是與ORM框架進行整合,這里選用mybatis,因此需要添加mybatis相關配置

  1. <!--配置mybatis的SqlSessionFactoryBean,注入datasource屬性引用的是routingDataSource-->

  2. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

  3.     <property name="dataSource" ref="routingDataSource”/>

  4.     <!--注意,如果你sql寫在xml中,需要打開以下配置,本案例寫在映射器接口上-->

  5.     <!--<property name="mapperLocations" value="classpath*:config/sqlmap/**/*.xml" />-->

  6. </bean>

  7. <!--配置MapperScannerConfigurer-->

  8. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

  9.     <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

  10.     <!--UserMapper和UserAccountMapper位于此包中-->

  11.     <property name="basePackage" value="io.github.tianshouzhi.routing.mapper"/>

  12. </bean>

UserMapper和UserAccountMapper,分別用于訪問db1.user表和db2.user_account表。之后我們就可以通過這兩個接口上添加@Routing注解,來讓其訪問不同的庫。

4 @Routing注解的基本使用

@Routing可以在方法上使用,也可以在類或者接口上使用,以下是Routing注解的使用說明

1 方法上添加@Routing注解

  1. public interface UserMapper {

  2.    @Routing("ds1”)

  3.    //通過@Routing注解,指定此方法走ds1數據源

  4.    public User selectById(@Param("id") int id);

  5.    //未添加注解,將走默認的數據源,因為在配置RoutingDataSource時,已經指定了默認的數據源是ds1,所以可以成功訪問user表;

  6.    //注意:如果默認的數據源是ds2,此方法將會報錯,因為user表位于db1中,而不是db2中

  7.    public int insert(User user);

  8. }

2 接口或者類上使用@Routing注解,內部定義的方法將都會繼承此注解

例如我們在UserAccountMapper接口上定義了@Routing注解,接口中定義的2個方法都會走ds2數據源訪問db2,而user_account位于db2中 :

  1. @Routing("ds2")

  2. public interface UserAccountMapper {

  3.    UserAccount selectById(@Param("id") int id);

  4.    int insert(UserAccount userAccount);

  5. }

提示:通常我們一個Mapper接口操作的都是某個庫中的表,因此建議直接在接口上添加@Routing注解,而不是每個方法單獨添加

3 接口/方法上同時添加@Routing注解,方法上的@Routing注解優先于接口上的Routing注解

  1. @Routing("ds2")

  2. public interface UserAccountMapper {

  3.    //使用接口上@Routing注解指定的ds2數據源

  4.    UserAccount selectById(@Param("id") int id);

  5.    

  6.    //使用方法上@Routing注解指定的ds1數據源

  7.    //注意:這是一個錯誤的示例,因為user_account表位于db2中

  8.    @Routing("ds1")

  9.    int insert(UserAccount userAccount);

  10. }

4 使用包級別的@Routing注解

如果項目的目錄結構劃分的比較好,操作不同的庫的Mapper接口,位于不同的包下。如:

  • com.tianshouzhi.mapper.db1包下都是操作db1的Mapper接口

  • com.tianshouzhi.mapper.db2包下都是操作db2的Mapper接口

此時你可以使用包級別的@Routing注解,在包下面創建一個package-info.java,從而無需在每個接口上都定義@Routing注解。例如:

注:對于@Routing注解而言,優先級滿足以下條件:方法>接口>包

5 在業務層方法調用

     Mapper映射器接口屬于dao層,通常dao層的代碼都是在service層進行調用的,業務層的接口也可以添加@Routing注解,如果沒有添加。則由調用的Mapper映射器方法、接口上的@Routing注解決定使用哪個ds,如果都沒有沒有定義,則使用默認的數據源

  1. public void business(int userId,int userAccountId) {

  2.       userAccountMapper.selectById(userAccountId);

  3.       userMapper.selectById(userId);

  4. }

6 業務層方法添加@Routing注解

業務層方法添加@Routing注解后,將忽略內部調用的Mapper映射器方法、接口上的Routing注解,如:

  1. //指定方法內部調用的映射器接口,都必須使用ds2

  2. @Routing("ds2")

  3. public void business(int userId,int userAccountId) {

  4.      //user_account表位于db2中,因此訪問可以成功

  5.      System.out.println(userAccountMapper.selectById(userAccountId));

  6.      //注意:user表位于db1中,這里強制使用ds2,因此將執行失敗

  7.      System.out.println(userMapper.selectById(userId));

  8. }

之所以一個方法使用了@Routing注解后,將會忽略內部調用的其他方法的@Routing注解,主要是為了與事務的語義兼容。

5 事務的支持

@Routing注解可以與spring的事務聯合使用,以下是聲明式事務@Transactional注解的案例,注意要保證事務中的方法必須都訪問的是同一個庫中的表。

以下是事務管理器的配置

  1. <!--事務管理器配置,注意datasource屬性引用的也是routingDataSource-->

  2. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

  3.     <property name="dataSource" ref="routingDataSource"/>

  4. </bean>

  5. <tx:annotation-driven />

1 只使用@Transactional注解,不使用@Routing注解

        這將使用我們配置的默認數據源,如果訪問了其他庫中的表,將會報錯,也就是說,使用了@Transactional注解后,也會忽略內部調用的其他方法的@Routing注解。

  1. @Transactional

  2. public void testDefaultTransaction(User user,UserAccount userAcccount) {

  3.    //默認數據源是ds1,可以訪問db1中user表,因此插入成功

  4.    userMapper.insert(user);

  5.    //注意:這個方法將執行失敗,事務將回滾,因為user_account位于db2中

  6.    userAccountMapper.insert(userAcccount)

  7. }

2 同時使用@Transactional/@Routing注解

spring事務管理器將會使用@Routing注解中指定的數據源來開啟事務

  1. @Transactional

  2. @Routing("ds2”) //使用ds2開啟事務

  3. public void testRoutingTransaction(User user,UserAccount userAcccount) {

  4.    userAccountMapper.insert(userAccount);

  5.    //注意:這個方法將執行失敗,事務將回滾,因為user位于db1中

  6.    userMapper.insert(user);

  7. }

6 RoutingDataSource的完整使用案例

完整使用案例,可從項目的開源github地址獲取:http://github.com/tianshouzhi/routing-datasource,也可以通過git 克隆項目源碼:

  1. git clone http://github.com/tianshouzhi/routing-datasource.git

相關代碼位于test目下:

以下是相關建表語句:

  1. CREATE DATABASE `db1`;

  2. CREATE TABLE `user` (

  3.   `id` int(11) NOT NULL AUTO_INCREMENT,

  4.   `name` varchar(255) NOT NULL

  5.   PRIMARY KEY (`id`)

  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  7. CREATE DATABASE `db2`;

  8. CREATE TABLE `user_account` (

  9.   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,

  10.   `account` varchar(255) DEFAULT NULL,

  11.   PRIMARY KEY (`id`)

  12. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

免費學習視頻歡迎關注云圖智聯:http://e.yuntuzhilian.com/



Java幫幫學習群生態

Java幫幫學習群生態

總有一款能幫到你

Java學習群

Java學習群

與大牛一起交流

大數據學習群

大數據學習群

在數據中成長

九點編程學習群

九點編程學習群

深夜九點學編程

python學習群

python學習群

人工智能,爬蟲

測試學習群

測試學習群

感受測試的魅力

Java幫幫生態承諾

Java幫幫生態承諾

一直堅守,不負重望

初心
勤儉
誠信
正義
分享
友鏈交換:加幫主QQ2524138991 留言即可 24小時內答復  
業司
教育資訊
會員登錄
獲取驗證碼
登錄
登錄
我的資料
留言
回到頂部