MySQL多數據源筆記5-ShardingJDBC實戰


Sharding-JDBC集分庫分表、讀寫分離、分布式主鍵、柔性事務和數據治理與一身,提供一站式的解決分布式關系型數據庫的解決方案。

從2.x版本開始,Sharding-JDBC正式將包名、Maven坐標、碼雲倉庫、Github倉庫和官方網站統一為io.shardingjdbc。

Sharding-JDBC是一款基於JDBC的數據庫中間件產品,對Java的應用程序無任何改造成本,只需配置分片規則即可無縫集成進遺留系統,使系統在數據訪問層直接具有分片化和分布式治理的能力。

Sharding-JDBC 2.x提供了全新的Orchestration模塊,關注數據庫和數據庫訪問層應用的治理。2.0.0在治理方面的主要更新是:

  1. 配置動態化。可以通過zookeeper或etcd作為注冊中心動態修改數據源以及分片規則。
  2. 數據治理。提供熔斷數據庫訪問程序對數據庫的訪問和禁用從庫的訪問的能力。
  3. 跟蹤系統支持。可以通過sky-walking等基於Opentracing協議的APM系統中查看Sharding-JDBC的調用鏈,並提供sky-walking的自動探針。
  4. 提供Sharding-JDBC的spring-boot-starter。

通過2.x提供的數據治理能力,sharding-jdbc的架構圖是:

左邊是部署架構圖,右邊是核心邏輯架構圖

分片規則配置

Sharding-JDBC的分片策略配置是自定義的,因此可以通過編程的方式最大限度的靈活調整。它並不僅支持=運算符分片,可支持BETWEEN和IN的運算符分片,支持將一條邏輯SQL最終散落至多個數據節點。同時支持多分片鍵,

例如:根據用戶ID分庫,訂單ID分表這種分庫分表結合的分片策略;或根據年分庫,月份+用戶區域ID分表這樣的多片鍵分片。

通過編程的方式定制分片規則雖然靈活,但配置起來略顯繁瑣。因此Sharding-JDBC又提供了In line表達式編寫分片策略的方式,用於配置集中化,以避免配置散落在配置文件和代碼中的情況。此外,它還提供了定制化的Spring命名空間和YAML進一步簡化配置。

Sharding-JDBC核心流程:

Sharding-JDBC是一個具有分庫分表功能的數據庫中間件。它通過JDBC擴展 => SQL解析 => SQL路由 => SQL改寫 => SQL執行 => 結果歸並的流程,
在SQL通過使用邏輯表,配合用戶配置的分片規則,將對數據庫訪問的真實SQL完全屏蔽。

 

  1JDBC擴展:

    將JDBC接口中的Connection和Statement(PreparedStatement)的對應關系從一對一轉換為一對多。因此一個邏輯SQL的執行,則有可能被拆分為多個執行結果集。

  2.SQL解析:

    分為詞法解析和語法解析。先通過詞法解析將SQL拆分為一個個不可再分的單詞。再使用語法解析器對SQL進行理解,並最終提煉出解析上下文。
    解析上下文包括表、選擇項、排序項、分組項、聚合函數、分頁信息、查詢條件以及可能需要修改的占位符的標記。

    Sharding-JDBC支持各種連接、聚合、排序、分組以及分頁的解析,並且可以有限度的支持子查詢。

  3.SQL路由:   

    根據解析上下文匹配用戶配置的分片策略,並生成路由路徑。目前支持分片路由、Hint路由、廣播路由、單播路由以及阻斷路由等方式。
    分片路由用於攜帶分片鍵的SQL路由,根據分片鍵的不同又可以划分為單片路由(分片操作符是等號)、多片路由(分片操作符是IN)和范圍路由(分片操作符是BETWEEN)。
    Hint路由用於通過程序的方式注入路由最終目的地的方式路由,可用於分片信息不包含在SQL中的場景。
    廣播路由用於SQL中不包含分片鍵的場景。根據SQL類型又可以划分為全庫廣播路由(SET AUTOCOMMIT=1)和全庫表廣播路由(DQL, DML, DDL)。
    單播路由用於獲取某一真實表信息的場景,如DESCRIBE table_name。
    阻斷路由用於屏蔽SQL對數據庫的操作,如USE db_name,因為Sharding-JDBC僅有一個邏輯數據源,無需切換。

  3.SQL改寫:

    將SQL改寫為在真實數據庫中可以正確執行的語句。SQL改寫分為正確性改寫和優化改寫。
    正確性改寫包括將邏輯表名稱替換為真實表名稱,將分頁信息的啟示取值和結束取值改寫,增加為排序、分組和自增主鍵使用的補列,將AVG改寫為SUM / COUNT等。
    優化改寫則是能將SQL改寫的更加適於在分布式的數據庫中執行,如將僅有分組的SQL增加排序字段,以便於將分組歸並從內存歸並轉化為流式歸並。

    正確性改寫包括將分表的邏輯表名稱替換為真實表名稱,修正分頁信息和增加補列。舉兩個例子:  

      • AVG計算。分布式場景,以avg1 + avg2 + avg3 / 3計算平均值並不正確,需要改寫為 (sum1 + sum2 + sum3) / (count1 + count2 + count3)。這就需要將包含AVG的SQL改寫為SUM和COUNT,並在結果歸並時重新計算平均值。

      • 分頁。假設每10條數據為一頁,取第2頁數據。在分片環境下獲取LIMIT 10, 10,歸並之后再根據排序條件取出前10條數據是不正確的結果。正確的做法是將分條件改寫為LIMIT 0, 20,取出所有前兩頁數據,再結合排序條件計算出正確的數據。因此越是獲取靠后數據,分頁的效率就會越低。有很多方法可避免使用LIMIT進行分頁。比如構建記錄行記錄數和行偏移量的二級索引,或使用上次分頁數據結尾ID作為下次查詢條件的分頁方式。

    優化改寫這里同樣舉兩個例子:

      • 單路由拒絕改寫。這是將SQL改寫挪到SQL路由之后的原因。當獲得路由結果之后,單路由的情況因為不涉及到結果歸並,因此分頁、補列等改寫都無需存在。尤其是分頁,無需將數據從第1條開始取,節省了網絡帶寬。

      • 流式歸並改寫。一會講到歸並時會說,這里先提一句,將僅包含GROUPBY的SQL改寫為GROUPBY + ORDERBY。

  4.SQL執行:

    通過多線程執行器異步執行,但同一個物理數據源的不同分表的SQL會采用同一連接的同一線程,以保證其事務的完整性。

    路由至真實數據源后,Sharding -JDBC將采用多線程並發執行SQL。它用3種執行引擎分別對應處理Statement,PreparedStatement和AddBatchPreparedStatement。

    Sharding-JDBC線程池放在一個名為ShardingContext的對象中,它的生命周期同ShardingDataSource保持一致。

    如果一個應用中創建了多個Sharding-JDBC的數據源,它們將持有不同的線程池。

  5.結果歸並:

    將多個執行結果集歸並以便於通過統一的JDBC接口輸出。結果歸並包括流式歸並、內存歸並和使用裝飾者模式的追加歸並這幾種方式。
    流式歸並用於簡單查詢、排序查詢、分組查詢以及排序和分組但排序項和分組項完全一致的場景,流式歸並的結果集的遍歷方式是通過每一次調用next方法取出,無需占用額外的內存。
    內存歸並僅用於排序項和分組項不一致的場景,需要將結果集中的所有數據加載至內存處理,如果結果集過多,會占用大量內存。
    使用裝飾者模式的追加歸並用於分頁,無論是簡單查詢、排序查詢還是分組查詢,包含分頁的SQL都會經過分頁的裝飾器處理分頁相關的結果歸並。

    Sharding-JDBC支持的結果歸並從功能上分為遍歷、排序、分組和分頁4種類型,它們是組合而非互斥的關系。從結構划分,可分為流式歸並內存歸並裝飾者歸並

    流式歸並和內存歸並是互斥的,裝飾者歸並可以在流式歸並和內存歸並之上做進一步的處理。

    流式歸並是將數據游標與結果集的游標保持一致,順序的從結果集中一條條的獲取正確的數據。遍歷和排序都是流式歸並,分組比較復雜,分為流式分組和內存分組。

    內存歸並則是需要將結果集的所有數據都遍歷並存儲在內存中,再通過內存歸並后,將內存中的數據偽裝成結果集返回。

    遍歷類型最為簡單,只需將多結果集組成鏈表,遍歷完成當前結果集后,將鏈表位置后移,繼續遍歷下一個結果集即可。

    排序類型稍微復雜,由於ORDER BY的原因,每個結果集自身數據是有序的,因此只需要將結果集當前游標指向的值排序即可。Sharding-JDBC在排序類型歸並時,將每個結果集的當前排序數據實現了比較器,並將其放入優先級隊列。

    每次JDBC調用next時,將隊列頂端的結果集出隊並next,然后獲取新的隊列頂端的結果集供JDBC獲取數據。

    分組類型最為復雜,分組歸並已經不屬於OLTP范疇,而更面向OLAP,但由於遺留系統使用很多,因此Sharding-JDBC還是將其實現。分組歸並分成流式分組歸並和內存分組歸並。流式分組歸並節省內存,但必須要求排序和分組的數據保持一致。

    如果GROUPBY和ORDER BY的內容不一致,則必須使用內存分組歸並。由於數據不是按照分組需要的順序取出,因此需要將結果集中的所有數據全部加載至內存。在SQL改寫時提到的僅有GROUP BY的SQL,會優化增加ORDER BY語句,即使將內存分組歸並優化為流式分組歸並的提升。

    無論是流式分組還是內存分組,對聚合的處理都是一致的。聚合分為比較、累加和平均值3種類型。比較聚合包括MAX和MIN,只返回最大(小)結果。累加聚合包括SUM和COUNT,需要將結果累加后返回。平均值聚合則是通過SQL改寫的SUM和COUNT計算,相關內容已在SQL改寫涵蓋,不再贅述。

    最后再聊一下裝飾者歸並他是對所有的結果集歸並進行統一的功能增強,目前裝飾者歸並只有分頁一種類型

    上述的所有歸並類型,都可能分頁或不分頁,因此可以通過裝飾者模式來增加分頁的能力。分頁歸並會將改寫的LIMIT中,不需要獲取的數據過濾掉

    Sharding-JDBC的分頁很容易產生誤解,很多人認為分頁會占用大量內存,因為Sharding-JDBC會因為分布式正確性的考量,將LIMIT 100000, 10改寫為LIMIT 0,  100010,產生Sharding-JDBC會將100010數據都加載到內存的錯覺。

    通過上面分析可知,會全部加載到內存的只有內存分組歸並這一種情況。其他情況都是通過流式獲取結果集數據的方式,因此Sharding-JDBC會通過結果集的next方法將無需取出的數據全部跳過,並不會將其存入內存

 

 

分布式主鍵

  傳統數據庫軟件開發中,主鍵自動生成技術是基本需求。而各大數據庫對於該需求也提供了相應的支持,比如MySQL的自增鍵。
  對於MySQL而言,分庫分表之后,不同表生成全局唯一的Id是非常棘手的問題。因為同一個邏輯表內的不同實際表之間的自增鍵是無法互相感知的,
  這樣會造成重復Id的生成。我們當然可以通過約束表生成鍵的規則來達到數據的不重復,但是這需要引入額外的運維力量來解決重復性問題,並使框架缺乏擴展性。

  目前有許多第三方解決方案可以完美解決這個問題,比如UUID等依靠特定算法自生成不重復鍵,或者通過引入Id生成服務等。
  但也正因為這種多樣性導致了Sharding-JDBC如果強依賴於任何一種方案就會限制其自身的發展。

  基於以上的原因,最終采用了以JDBC接口來實現對於生成Id的訪問,而將底層具體的Id生成實現分離出來。

 

  使用方法分為設置自動生成鍵和獲取生成鍵兩部分:

      設置自動生成鍵:

        配置自增列,代碼如下:  

TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration();
tableRuleConfig.setLogicTable("t_order");
tableRuleConfig.setKeyGeneratorColumnName("order_id");

  設置Id生成器的實現類,該類必須實現io.shardingjdbc.core.keygen.KeyGenerator接口。

  配置全局生成器(com.xx.xx.KeyGenerator),代碼如下:

  

ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.setDefaultKeyGeneratorClass("com.xx.xx.KeyGenerator");

有時候我們希望部分表的Id生成器與全局Id生成器不同,比如t_order_item表希望使用com.xx.xx.OtherKeyGenerator來生成Id:

  

TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration();
tableRuleConfig.setLogicTable("t_order");
tableRuleConfig.setKeyGeneratorColumnName("order_id");
tableRuleConfig.setKeyGeneratorClass("com.xx.xx.OtherKeyGenerator");

這樣t_order就使用com.xx.xx.KeyGenerator生成Id,而t_order_item使用com.xx.xx.OtherKeyGenerator生成Id。

 

獲取自動生成鍵:

  通過JDBC提供的API來獲取。對於Statement來說調用```statement.execute("INSERT ...", Statement.RETURN_GENERATED_KEYS)```

  來通知需要返回的生成的鍵值。對於PreparedStatement則是```connection.prepareStatement("INSERT ...", Statement.RETURN_GENERATED_KEYS)```

  調用```statement.getGeneratedKeys()```來獲取鍵值的ResultSet。

 

默認的分布式主鍵生成器:  

  類名稱:io.shardingjdbc.core.keygen.DefaultKeyGenerator

  該生成器采用snowflake算法實現,生成的數據為64bit的long型數據。
  在數據庫中應該用大於等於64bit的數字類型的字段來保存該值,比如在MySQL中應該使用BIGINT。

  其二進制表示形式包含四部分,從高位到低位分表為:1bit符號位(為0),41bit時間位,10bit工作進程位,12bit序列位。

  具體可以去看官方文檔

 

  分布式主鍵最獨立的部分是生成策略,Sharding-JDBC提供靈活的配置分布式主鍵生成策略方式。在分片規則配置模塊可配置每個表的主鍵生成策略,默認使用snowflake。

  通過策略生成的分布式主鍵可以無縫的融入JDBC協議,它實現了Statement的getGeneratedKeys方法,將其返回改寫后的Result和ResultMetaData,將Sharding-JDBC生成的分布式主鍵偽裝為數據庫生成的自增主鍵返回。

  SQL解析時,需要根據分布式主鍵配置策略判斷是否在邏輯SQL中已包含主鍵列,如果未包含則需要將INSERTItems和INSERT Values的最后位置寫入解析上下文。

  SQL改寫時,將根據解析上下文中的位置改寫SQL,增加未包含的主鍵列名稱和值。如果是Statement則在INSERT Values后追加生成后的分布式主鍵;如果是PreparedStatement則在INSERT Values后追加?,並在傳入的參數后追加生成后的分布式主鍵。

 

ShardingJDBC實戰

 

  首先去github上面下載官方提供的練習,github地址為:https://github.com/shardingjdbc  下載sharding-jdbc-example練習中的sharding-jdbc-spring-namespace-example/sharding-jdbc-spring-namespace-mybatis-example,sharding-jdbc-doc是官方文檔。

 

  自己搭建一個SSM工程。以下只展示關鍵的配置和代碼。

  jdbc.properties配置如下:

  

jdbc_url_1=jdbc:mysql://localhost:3306/db_1?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
jdbc_url_2=jdbc:mysql://localhost:3307/db_2?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
jdbc_url_3=jdbc:mysql://localhost:3308/db_3?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
jdbc_username=root
jdbc_password=root

 

  spring-cfg.xml的配置,配置說明已經在配置文件注釋說明好了。如下代碼:

  

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:sharding="http://shardingjdbc.io/schema/shardingjdbc/sharding"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    http://shardingjdbc.io/schema/shardingjdbc/sharding
    http://shardingjdbc.io/schema/shardingjdbc/sharding/sharding.xsd">


    <!--掃描注解生成bean-->
    <context:annotation-config/>
    <!--包掃描-->
    <context:component-scan base-package="com.coder520"/>

    <context:property-placeholder location="classpath:jdbc.properties"/>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="shardingDataSource"/>
        <property name="mapperLocations" value="classpath:com/coder520/**/**.xml"/>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.coder520.*.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <!--聲明事務管理 采用注解方式-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="shardingDataSource"/>
    </bean>

    <!--開啟切面代理-->
    <aop:aspectj-autoproxy/>
    

    <!--主數據庫設置-->
    <bean id="ds_0" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close" init-method="init">
        <property name="url" value="${jdbc_url_1}"/>
        <property name="username" value="${jdbc_username}"/>
        <property name="password" value="${jdbc_password}"/>
    </bean>
    <!--從數據庫設置-->
    <bean id="ds_1" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close" init-method="init">
        <property name="url" value="${jdbc_url_2}"/>
        <property name="username" value="${jdbc_username}"/>
        <property name="password" value="${jdbc_password}"/>
    </bean>
    <!--從數據庫設置-->
    <bean id="ds_2" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close" init-method="init">
        <property name="url" value="${jdbc_url_3}"/>
        <property name="username" value="${jdbc_username}"/>
        <property name="password" value="${jdbc_password}"/>
    </bean>

    <!--分庫策略,sharding-column這里根據user_id(這列屬性不能是String,只能是整型)分庫,precise-algorithm-class是分庫算法,-->
    <sharding:standard-strategy id="databaseShardingStrategy" sharding-column="user_id"
                                precise-algorithm-class="com.coder520.sharding.PreciseModuloDatabaseShardingAlgorithm"/>
    <!--分表策略,sharding-column這里根據order_id(這列屬性不能是String,只能是整型)分表,precise-algorithm-class是分表算法-->
    <sharding:standard-strategy id="tableShardingStrategy" sharding-column="order_id"
                                precise-algorithm-class="com.coder520.sharding.PreciseModuloTableShardingAlgorithm"/>

    <!--自己寫shardingDataSource嵌入Spring里面-->
    <sharding:data-source id="shardingDataSource">
        <!--告訴shardingjdbc你所有庫的名字,這里為什么要從0開始命名呢?因為shardingjdbc的分庫分表策略是求余來分配的,比如user_id求余3有0,1,2,所以只能這樣命名-->
        <sharding:sharding-rule data-source-names="ds_0,ds_1,ds_2">
            <sharding:table-rules>
                <!--ds_${0..2}.t_order_${0..2},這里用到了In line表達式${0..2},這里是用了笛卡爾乘積運算,比如ds_0.t_order_0 ds_0.t_order_1 ds_0.t_order_2-->
                <!--logic-table是邏輯表 actual-data-nodes是實際的數據源節點-->
                <!--generate-key-column生成主鍵列,這是一個全局序列號,是唯一的,這是shardingjdbc自動會幫我們生成的。避免我們自己自增造成id重復-->
                <sharding:table-rule logic-table="t_order" actual-data-nodes="ds_${0..2}.t_order_${0..2}"
                                     database-strategy-ref="databaseShardingStrategy" table-strategy-ref="tableShardingStrategy"
                                        generate-key-column="order_id"/>
                <sharding:table-rule logic-table="t_order_item" actual-data-nodes="ds_${0..2}.t_order_item_${0..2}"
                                     database-strategy-ref="databaseShardingStrategy" table-strategy-ref="tableShardingStrategy"
                                        generate-key-column="order_item_id"/>
            </sharding:table-rules>
        </sharding:sharding-rule>
    </sharding:data-source>
    
</beans>

 

接着在工程中自己建立一個包,把github上的兩個分庫分表的算法類復制進來,因為spring-cfg.xml中根據這兩個類來分庫分表的,我這里的包是com.coder520.sharding,

分庫算法類,如下:

package com.coder520.sharding;

import io.shardingjdbc.core.api.algorithm.sharding.PreciseShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.standard.PreciseShardingAlgorithm;

import java.util.Collection;

public final class PreciseModuloDatabaseShardingAlgorithm implements PreciseShardingAlgorithm<Integer> {
//Collection<String> availableTargetNames是我們在配置文件配置的數據源  PreciseShardingValue<Long> shardingValue是傳進來配置文件邏輯表的邏輯表名和傳進來user_id列名和值
@Override 
public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<Integer> shardingValue) { for (String each : availableTargetNames) {
        //這里求余3是因為我這里在spring-cfg.xml中配置的三個數據庫,這里的意思是將數據分到哪一個庫中,比如user_id為51,則分配到第一個數據庫中,其他數據庫中沒用信息
        if (each.endsWith(shardingValue.getValue() % 3 + "")) {
            return each;
        }
     }
    thrownew UnsupportedOperationException();
  }
}

分表算法類代碼如下:

package com.coder520.sharding;

import io.shardingjdbc.core.api.algorithm.sharding.PreciseShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.standard.PreciseShardingAlgorithm;

import java.util.Collection;

public final class PreciseModuloTableShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
    
  //
Collection<String> availableTargetNames是我們在配置文件配置的數據源 PreciseShardingValue<Long> shardingValue是傳進來配置文件邏輯表的邏輯表名和傳進來user_id列名和值
@Override 
public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<Long> shardingValue) { for (String each : availableTargetNames) {
        //這里求余3是因為我這里有三個庫,分別是我們在spring-cfg.xml配置的
if (each.endsWith(shardingValue.getValue() % 3 + "")) { return each; } } throw new UnsupportedOperationException(); } }

 

  springmvc.xml和web.xml省略。自己配置。

  

  接着使用github上面的下載下來的小demo來練習

 

  controller代碼如下:  

package com.coder520.order.controller;

import com.coder520.order.service.DemoService;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;

/**
 * Created by cong on 2018/3/19.
 */
public class OrderController {

    @Resource
    private DemoService demoService;

    @RequestMapping("/test")
    public void test(){
        demoService.demo();
    }

}

 

service代碼如下:

package com.coder520.order.service;


import com.coder520.order.dao.OrderItemRepository;
import com.coder520.order.dao.OrderRepository;
import com.coder520.order.entity.Order;
import com.coder520.order.entity.OrderItem;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@Service
public class DemoService {
    
    @Resource
    private OrderRepository orderRepository;
    
    @Resource
    private OrderItemRepository orderItemRepository;
    
    public void demo() {
        orderRepository.createIfNotExistsTable();
        orderItemRepository.createIfNotExistsTable();
    }
}

 

entity和dao都是用github上的那個練習下載下來的的,如下:

  Order類:

  

public final class Order {
    
    private long orderId;
    
    private int userId;
    
    private String status;
    
    public long getOrderId() {
        return orderId;
    }
    
    public void setOrderId(final long orderId) {
        this.orderId = orderId;
    }
    
    public int getUserId() {
        return userId;
    }
    
    public void setUserId(final int userId) {
        this.userId = userId;
    }
    
    public String getStatus() {
        return status;
    }
    
    public void setStatus(final String status) {
        this.status = status;
    }
    
    @Override
    public String toString() {
        return String.format("order_id: %s, user_id: %s, status: %s", orderId, userId, status);
    }
}

OderItem類如下:

package com.coder520.order.entity;

public final class OrderItem {
    
    private long orderItemId;
    
    private long orderId;
    
    private int userId;
    
    private String status;
    
    public long getOrderItemId() {
        return orderItemId;
    }
    
    public void setOrderItemId(final long orderItemId) {
        this.orderItemId = orderItemId;
    }
    
    public long getOrderId() {
        return orderId;
    }
    
    public void setOrderId(final long orderId) {
        this.orderId = orderId;
    }
    
    public int getUserId() {
        return userId;
    }
    
    public void setUserId(final int userId) {
        this.userId = userId;
    }
    
    public String getStatus() {
        return status;
    }
    
    public void setStatus(final String status) {
        this.status = status;
    }
    
    @Override
    public String toString() {
        return String.format("order_item_id:%s, order_id: %s, user_id: %s, status: %s", orderItemId, orderId, userId, status);
    }
}

dao和Mapper.xml文件自己去找到那個下載好的DEMO哪里復制進來

 

 

注意數據庫表不用建立了,因為orderRepository.createIfNotExistsTable();orderItemRepository.createIfNotExistsTable();這里判斷表存不存在,不存在自己會建立好的。但是db_1,db_2,db_3,這三個數據庫必須建立好。

如下圖:

 

 接着運行,可以看到每一個數據庫表已經建立好了,如下圖:

 

  接着進行插入測試,修改service的代碼,如下:

  

package com.coder520.order.service;


import com.coder520.order.dao.OrderItemRepository;
import com.coder520.order.dao.OrderRepository;
import com.coder520.order.entity.Order;
import com.coder520.order.entity.OrderItem;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@Service
public class DemoService {
    
    @Resource
    private OrderRepository orderRepository;
    
    @Resource
    private OrderItemRepository orderItemRepository;
    
    public void demo() {

        List<Long> orderIds = new ArrayList<>(10);
        System.out.println("1.Insert--------------");
        for (int i = 0; i < 10; i++) {
            Order order = new Order();
            order.setUserId(51);
            order.setStatus("INSERT_TEST");
            orderRepository.insert(order);
            long orderId = order.getOrderId();
            orderIds.add(orderId);
            
            OrderItem item = new OrderItem();
            item.setOrderId(orderId);
            item.setUserId(51);
            item.setStatus("INSERT_TEST");
            orderItemRepository.insert(item);
        }
        System.out.println(orderItemRepository.selectAll());
        System.out.println("2.Delete--------------");
        for (Long each : orderIds) {
            orderRepository.delete(each);
            orderItemRepository.delete(each);
        }
        System.out.println(orderItemRepository.selectAll());
        orderItemRepository.dropTable();
        orderRepository.dropTable();
    }
}

 

 接着在啟動,可以看到數據全部被分配到第一個數據庫中,因為分局分庫算法user_id為51%3=0如下圖:

  

那么是怎么個分庫的呢?我們先把在代碼中將user_id再設置成52。

那么下面進行打斷點查看分庫分表的過程。分別在那兩個分庫分表策略打斷點,如下:

可以看到斷點進來了,可以看到首先是找數據庫,

 

 最終可以看到分配到最終的數據庫,如下:

 

接着繼續打斷點,進到了分表策略這個類里面了

這是傳進來的是邏輯表名和分表的依據order_id,分表依據order_id跟3求余,如下圖:

 

 

 最后選中求余后的表如下:

 

這里要注重說一點,each.endsWith是根據你數據源的尾號是否跟匹配的訂單號求余后是否相等,相等了就說明被分配到這里了。比如ds_o和求余后0相等,

最后我們可以看到數據不再分配到第一個數據庫了,而是本配到第二個數據庫了,因為52%3=1.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM