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

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

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

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

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

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

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

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

分片規則配置算法

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

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

經過編程的方式定製分片規則雖然靈活,但配置起來略顯繁瑣。所以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.

相關文章
相關標籤/搜索