1.1、ShardingSphere簡介
sharding-jdbc是ShardingSphere的其中一個模塊,摘抄官網一段簡介:
(官方中文文檔:https://shardingsphere.apache.org/document/current/cn/features/sharding/concept/sql/)
ShardingSphere是一套開源的分布式數據庫中間件解決方案組成的生態圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(計划中)這3款相互獨立的產品組成。 他們均提供標准化的數據分片、分布式事務和數據庫治理功能,可適用於如Java同構、異構語言、容器、雲原生等各種多樣化的應用場景。
ShardingSphere定位為關系型數據庫中間件,旨在充分合理地在分布式的場景下利用關系型數據庫的計算和存儲能力,而並非實現一個全新的關系型數據庫。 它與NoSQL和NewSQL是並存而非互斥的關系。NoSQL和NewSQL作為新技術探索的前沿,放眼未來,擁抱變化,是非常值得推薦的。反之,也可以用另一種思路看待問題,放眼未來,關注不變的東西,進而抓住事物本質。 關系型數據庫當今依然占有巨大市場,是各個公司核心業務的基石,未來也難於撼動,我們目前階段更加關注在原有基礎上的增量,而非顛覆。
1.2、sharding-jdbc簡介
定位為輕量級Java框架,在Java的JDBC層提供的額外服務。 它使用客戶端直連數據庫,以jar包形式提供服務,無需額外部署和依賴,可理解為增強版的JDBC驅動,完全兼容JDBC和各種ORM框架。
-
- 適用於任何基於Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
- 基於任何第三方的數據庫連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
- 支持任意實現JDBC規范的數據庫。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
功能列表:
數據分片
-
- 分庫 & 分表
- 讀寫分離
- 分片策略定制化
- 無中心化分布式主鍵
分布式事務
-
- 標准化事務接口
- XA強一致事務
- 柔性事務
數據庫治理
-
- 配置動態化
- 編排 & 治理
- 數據脫敏
- 可視化鏈路追蹤
- 彈性伸縮(規划中)
1.3、sharding-jdbc核心概念
-
- 邏輯表(LogicTable):進行水平拆分的時候同一類型(邏輯、數據結構相同)的表的總稱。例:訂單數據根據主鍵尾數拆分為10張表,分別是
t_order_0
到t_order_9
,他們的邏輯表名為t_order
。 - 真實表(ActualTable):在分片的數據庫中真實存在的物理表。即上個示例中的
t_order_0
到t_order_9
。 - 數據節點(DataNode):數據分片的最小單元。由數據源名稱和數據表組成,例:
ds_0.t_order_0
。 - 動態表(DynamicTable):邏輯表和物理表不一定需要在配置規則中靜態配置。如,按照日期分片的場景,物理表的名稱隨着時間的推移會產生變化。
-
廣播表:指所有的分片數據源中都存在的表,表結構和表中的數據在每個數據庫中均完全一致。適用於數據量不大且需要與海量數據的表進行關聯查詢的場景,例如:字典表。
- 綁定表(BindingTable):指分片規則一致的主表和子表。例如:
t_order
表和t_order_item
表,均按照order_id
分片,則此兩張表互為綁定表關系。綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升。舉例說明,如果SQL為:
- 邏輯表(LogicTable):進行水平拆分的時候同一類型(邏輯、數據結構相同)的表的總稱。例:訂單數據根據主鍵尾數拆分為10張表,分別是
SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
在不配置綁定表關系時,假設分片鍵order_id
將數值10路由至第0片,將數值11路由至第1片,那么路由后的SQL應該為4條,它們呈現為笛卡爾積:
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
在配置綁定表關系后,路由的SQL應該為2條:
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
其中t_order
在FROM的最左側,ShardingSphere將會以它作為整個綁定表的主表。 所有路由計算將會只使用主表的策略,那么t_order_item
表的分片計算將會使用t_order
的條件。故綁定表之間的分區鍵要完全相同。
-
- 分片鍵(ShardingColumn):分片字段用於將數據庫(表)水平拆分的字段,支持單字段及多字段分片。例如上例中的order_id。
- 分片算法(ShardingAlgorithm):進行水平拆分時采用的算法,分片算法需要應用方開發者自行實現,可實現的靈活度非常高。目前提供4種分片算法。由於分片算法和業務實現緊密相關,因此並未提供內置分片算法,而是通過分片策略將各種場景提煉出來,提供更高層級的抽象,並提供接口讓應用開發者自行實現分片算法。
1、精確分片算法 對應PreciseShardingAlgorithm,必選,用於處理使用單一鍵作為分片鍵的=與IN進行分片的場景。需要配合StandardShardingStrategy使用。 2、范圍分片算法 對應RangeShardingAlgorithm,可選,用於處理使用單一鍵作為分片鍵的BETWEEN AND進行分片的場景。如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫路由處理。需要配合StandardShardingStrategy使用。 3、復合分片算法 對應ComplexKeysShardingAlgorithm,用於處理使用多鍵作為分片鍵進行分片的場景,包含多個分片鍵的邏輯較復雜,需要應用開發者自行處理其中的復雜度。需要配合ComplexShardingStrategy使用。 4、Hint分片算法 對應HintShardingAlgorithm,用於處理使用Hint行分片的場景。需要配合HintShardingStrategy使用。
-
- 分片策略(ShardingStrategy):包含分片鍵和分片算法,由於分片算法的獨立性,將其獨立抽離。真正可用於分片操作的是分片鍵 + 分片算法,也就是分片策略。目前提供5種分片策略。
1、標准分片策略 對應StandardShardingStrategy。提供對SQL語句中的=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片算法。PreciseShardingAlgorithm是必選的,用於處理=和IN的分片。RangeShardingAlgorithm是可選的,用於處理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫路由處理。 2、復合分片策略 對應ComplexShardingStrategy。復合分片策略。提供對SQL語句中的=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片鍵,由於多分片鍵之間的關系復雜,因此並未進行過多的封裝,而是直接將分片鍵值組合以及分片操作符透傳至分片算法,完全由應用開發者實現,提供最大的靈活度。 3、行表達式分片策略 對應InlineShardingStrategy。使用Groovy的表達式,提供對SQL語句中的=和IN的分片操作支持,只支持單分片鍵。對於簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開發,如: t_user_$->{u_id % 8} 表示t_user表根據u_id模8,而分成8張表,表名稱為t_user_0到t_user_7。 4、Hint分片策略 對應HintShardingStrategy。通過Hint而非SQL解析的方式分片的策略。 5、不分片策略 對應NoneShardingStrategy。不分片的策略。
二、 Sharding-JDBC 實戰
2.1、引入依賴
核心依賴是:sharding-jdbc-core
注意組織名稱(groupId)是 org.apache.shardingsphere,捐獻給 Apache 之后。
io.shardingjdbc :更名之前(廢棄)
io.shardingsphere:更名之后(廢棄)
包名和某些類有差異,如果升級需要注意,import 的包名都需要修改。
Apache 的包為 Spring Boot 提供了 starter 的依賴:
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.1.1</version> </dependency>
2.2、 API 使用
先看 API 是怎么用的,也就是自己像 JDBC 的代碼一樣創建數據源,創建連接。后面再看在 Spring 里面怎么使用。在寫之前我們先來寫下原生的JDBC連接操作數據庫方法,到時可以跟sharding-jdbc的比較
public class JdbcTest { public static void main(String[] args) { Connection connection=null; Statement statement=null; Student student = new Student(); try { // 注冊 JDBC 驅動 Class.forName("com.mysql.jdbc.Driver"); // 打開連接 connection = DriverManager.getConnection("jdbc:mysql://192.168.2.106:3306/ljxmycat", "root", "root"); // 執行查詢 statement = connection.createStatement(); String sql = "SELECT sid, name,qq FROM student where sid = 1"; ResultSet rs = statement.executeQuery(sql); // 獲取結果集 while (rs.next()) { Integer sid = rs.getInt("sid"); String name = rs.getString("name"); String qq = rs.getString("qq"); student.setSid(sid); student.setName(name); student.setQq(qq); } System.out.println(student.toString()); rs.close(); statement.close(); connection.close(); }catch (SQLException se){ se.printStackTrace(); }catch (Exception e){ e.printStackTrace(); }finally { try { if (statement != null) { statement.close(); } } catch (SQLException se2) { } try { if (connection != null) { connection.close(); } } catch (SQLException se) { se.printStackTrace(); } } } }
2.2.1 、數據分片
public class ShardJDBCTest { public static void main(String[] args) throws SQLException { // 配置真實數據源 Map<String, DataSource> dataSourceMap = new HashMap<>(); // 配置第一個數據源 DruidDataSource dataSource1 = new DruidDataSource(); dataSource1.setDriverClassName("com.mysql.jdbc.Driver"); dataSource1.setUrl("jdbc:mysql://192.168.2.106:3306/ljxmycat0"); dataSource1.setUsername("root"); dataSource1.setPassword("root"); dataSourceMap.put("ljxmycat0", dataSource1); // 配置第二個數據源 DruidDataSource dataSource2 = new DruidDataSource(); dataSource2.setDriverClassName("com.mysql.jdbc.Driver"); dataSource2.setUrl("jdbc:mysql://192.168.2.106:3306/ljxmycat1"); dataSource2.setUsername("root"); dataSource2.setPassword("root"); dataSourceMap.put("ljxmycat1", dataSource2); // 配置Order表規則 TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("student", "ljxmycat${0..1}.student"); // 分庫策略,使用inline實現 InlineShardingStrategyConfiguration dataBaseInlineStrategy = new InlineShardingStrategyConfiguration("sid", "ljxmycat${sid % 2}"); orderTableRuleConfig.setDatabaseShardingStrategyConfig(dataBaseInlineStrategy); // 分表策略,使用inline實現(沒有分表,為什么不分表?) InlineShardingStrategyConfiguration tableInlineStrategy = new InlineShardingStrategyConfiguration("sid", "student"); orderTableRuleConfig.setTableShardingStrategyConfig(tableInlineStrategy); // 添加表配置 ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig); // 獲取數據源對象 DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties()); String sql = "SELECT * from student WHERE qq=?"; try { Connection conn = dataSource.getConnection(); PreparedStatement preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, "166669999"); System.out.println(); try (ResultSet rs = preparedStatement.executeQuery()) { while (rs.next()) { // %2結果,路由到 System.out.println("---------name:" + rs.getString(2)); } } } catch (Exception e) { e.printStackTrace(); } } }
ShardingDataSourceFactory 利用 ShardingRuleConfiguration 創建數據源。ShardingRuleConfiguration 可以包含多個 TableRuleConfiguration(多張表),每張表都可以通過 ShardingStrategyConfiguration 設置自己的分庫和分表策略。
有了數據源,就可以走 JDBC 的流程了。
2.2.2 、讀寫分離
public class MasterSlaveTest { public static void main(String[] args) throws SQLException { // 配置真實數據源 Map<String, DataSource> dataSourceMap = new HashMap<>(); // 配置第一個數據源 DruidDataSource dataSource1 = new DruidDataSource(); dataSource1.setDriverClassName("com.mysql.jdbc.Driver"); dataSource1.setUrl("jdbc:mysql://192.168.2.105:3306/ljxmycat"); dataSource1.setUsername("root"); dataSource1.setPassword("root"); dataSourceMap.put("master0", dataSource1); // 配置第二個數據源 DruidDataSource dataSource2 = new DruidDataSource(); dataSource2.setDriverClassName("com.mysql.jdbc.Driver"); dataSource2.setUrl("jdbc:mysql://192.168.2.106:3306/ljxmycat"); dataSource2.setUsername("root"); dataSource2.setPassword("root"); dataSourceMap.put("slave0", dataSource2); // 配置讀寫分離規則 MasterSlaveRuleConfiguration masterSlaveRuleConfig = new MasterSlaveRuleConfiguration("qs_master_slave", "master0", Arrays.asList("slave0")); // 獲取數據源對象 DataSource dataSource = MasterSlaveDataSourceFactory.createDataSource(dataSourceMap, masterSlaveRuleConfig, new Properties()); Connection conn = dataSource.getConnection(); String selectSql = "SELECT * from student where qq=?"; try { PreparedStatement preparedStatement = conn.prepareStatement(selectSql); // 直接在 slave 128 ds0 插入主節點沒有的數據: insert into t_order(order_id, user_id) value(26732,26732) preparedStatement.setString(1, "466669999"); System.out.println(); try (ResultSet rs = preparedStatement.executeQuery()) { while (rs.next()) { System.out.println("---------name:" + rs.getString(2)); } } } catch (Exception e) { e.printStackTrace(); } } }
總結一下:
在 JDBC API 中使用,我們可以直接創建數據源。如果在 Spring 中使用,我們自定義的數據源怎么定義使用呢?因為數據源是容器管理的,所以需要通過注解或者 xml 配置文件注入。
2.3 、Spring 中使用
先來總結一下,第一個,使用的數據源需要用 Sharding-JDBC 的數據源。而不是容器或者 ORM 框架定義的。這樣才能保證動態選擇數據源的實現。當然,流程是先由 Sharding-JDBC 定義,再交給 Druid 放進池子里,再交給 MyBatis,最后再注入到 Spring。最外層是 Spring,因為代碼是從 Spring 開始調用的。第二個,因為 Sharding-JDBC 是工作在客戶端的,所以我們要在客戶端配置分庫分表的策略。跟 Mycat 不一樣的是,Sharding-JDBC 沒有內置各種分片策略和算法,需要我們通過表達式或者自定義的配置文件實現。總體上,需要配置的就是這兩個,數據源和分片策略。配置的方式是多種多樣的,在官網也有詳細的介紹,大家可以根據項目的實際情況進行選擇。
2.3.1、 Java 配置
官網中關於java配置寫的很詳細,反正我們現在是springboot的時代,這里我也賴寫了,它的特點是非常靈活,我們可以實現各種定義的分片策略。但是缺點是,如果把數據源、策略都配置在 Java Config 中,就出現了硬編碼,在修改的時候比較麻煩。
2.3.2 、Spring Boot 配置
在springboot中我們是直接使用 Spring Boot 的 application.properties 來配置,這個要基於 starter 模塊。把數據源和分庫分表策略都配置在 properties 文件中。這種方式配置比較簡單,但是不能實現復雜的分片策略,不夠靈活。
2.3.3 、yml 配置
使用 Spring Boot 的 yml 配置(shardingjdbc.yml),也要依賴 starter模塊。當然我們也可以結合不同的配置方式,比如把分片策略放在 JavaConfig 中,數據源配置在 yml 中或 properties 中。
2.4 、Sharding-JDBC 分片案例驗證
在同一個服務器上建兩個庫
數據庫ds0,ds1 CREATE TABLE `user_info` ( `user_id` bigint(128) NOT NULL, `user_name` varchar(45) DEFAULT NULL, `account` varchar(45) NOT NULL, `password` varchar(45) DEFAULT NULL, PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `t_order` ( `order_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, PRIMARY KEY (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `t_order_item` ( `item_id` int(11) NOT NULL, `order_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, PRIMARY KEY (`item_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `t_config` ( `config_id` int(16) NOT NULL AUTO_INCREMENT, `para_name` varchar(255) DEFAULT NULL, `para_value` varchar(255) DEFAULT NULL, `para_desc` varchar(255) DEFAULT NULL, PRIMARY KEY (`config_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; truncate table user_info; truncate table t_order; truncate table t_order_item; truncate table t_config;
使用以下配置打印路由信息:
spring.shardingsphere.props.sql.show=true
如果直接配置mysql數據源用下面配置就可以:
spring.datasource.url=jdbc:mysql://localhost:3306/ds0 spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver
但是我們現在用的是sharding--jdbc數據源,用法就有變化:
我們可以定義默認的分庫分表策略(配置中注釋了),例如:用 user_id 作為分片鍵。這里用到了一種分片策略的實現,叫做行內表達式。我們對 user_id 取模,然后選擇數據庫。如果模 2 等於 0,在第一個數據庫中。模 2 等於 1,在第二個數據庫中。
數據源名稱是行內表達式組裝出來的。
spring.shardingsphere.sharding.tables.user_info.actual-data-nodes=ds$->{0..1}.user_info
對於不同的表,也可以單獨配置分庫策略( databaseStrategy )和分表策略(tableStrategy)。案例中只有分庫沒有分表,所以沒定義 tableStrategy
2.4.1 、取模分片
首先我們來驗證一下 user_info 表的取模分片(modulo ['mɔdjuləu])。我們根據 user_id,把用戶數據划分到兩個數據節點上。在本地創建兩個數據庫 ds0 和 ds1,表user_info在上面已經有說明過;
這里只定義了分庫策略,沒有定義單庫內的分表策略,兩個庫都是相同的表名。 路由的結果:ds0.user_info,ds1.user_info。如果定義了分庫策略,兩個庫里面都有兩張表,那么路由的結果可能是 4 種:ds0.user_info0,ds0.user_info1;ds1. user_info0, ds1. user_info1
spring.shardingsphere.sharding.tables.user_info.actual-data-nodes=ds$->{0..1}.user_info
spring.shardingsphere.sharding.tables.user_info.databaseStrategy.inline.shardingColumn=user_id
spring.shardingsphere.sharding.tables.user_info.databaseStrategy.inline.algorithm-expression=ds${user_id % 2}
首先兩個數據庫的 user_info 表里面沒有任何數據。配置完后的applicateion.properties配置文件 如下
# MyBatis配置 mybatis.mapper-locations=classpath:mapper/*.xml mybatis.config-location=classpath:mybatis-config.xml spring.shardingsphere.props.sql.show=true # 數據源配置 spring.shardingsphere.datasource.names=ds0,ds1 spring.shardingsphere.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds0.url=jdbc:mysql://localhost:3306/ds0 spring.shardingsphere.datasource.ds0.username=root spring.shardingsphere.datasource.ds0.password=root spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds1.url=jdbc:mysql://localhost:3306/ds1 spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=root # 默認策略 spring.shardingsphere.sharding.tables.user_info.actual-data-nodes=ds$->{0..1}.user_info spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds${user_id % 2}
pom.xml導入相關包
<dependencies>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>4.6.2</version>
</dependency>
</dependencies>
創建實體類
ublic class UserInfo { private Long userId; private String userName; private String account; private String password; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName == null ? null : userName.trim(); } public String getAccount() { return account; } public void setAccount(String account) { this.account = account == null ? null : account.trim(); } public String getPassword() { return password; } public void setPassword(String password) { this.password = password == null ? null : password.trim(); } @Override public String toString() { return " -------- UserInfo { " + "userId=" + userId + ", userName='" + userName + '\'' + ", account='" + account + '\'' + ", password='" + password + '\'' + '}'; } }
創建service
@Service public class UserService { @Resource UserInfoMapper userInfoMapper; public static Long userId = 1L; public void insert() { for (int i = 1; i <= 100; i++) { UserInfo userInfo = new UserInfo(); // userInfo.setUserId(userId); userInfo.setAccount("account" + i); userInfo.setPassword("password" + i); userInfo.setUserName("name" + i); userId++; userInfoMapper.insert(userInfo); } } public UserInfo getUserInfoByUserId(Long id){ return userInfoMapper.selectByPrimaryKey(id); } public List<UserInfo> selectByRange(Long firstId, Long lastId){ return userInfoMapper.selectByRange(firstId, lastId); } }
創建mapper
@Mapper public interface UserInfoMapper { int insert(UserInfo record); UserInfo selectByPrimaryKey(Long userId); List<UserInfo> selectByRange(@Param("firstId") Long firstId, @Param("lastId") Long lastId); }
在mapper中添加插入語句
<insert id="insert" parameterType="com.ghy.springbootsharding.entity.UserInfo"> insert into user_info (user_name, account, password) values ( #{userName,jdbcType=VARCHAR}, #{account,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}) </insert>
驗證代碼
/** * 演示取模的分庫分表策略 */ @RunWith(SpringRunner.class) @SpringBootTest @MapperScan(basePackages = "com.ghy.springbootsharding.mapper") public class UserShardingTest { @Resource UserService userService; @Test public void insert(){ userService.insert(); } }
2.4.2 、綁定表
第二種是綁定表,也就是父表和子表有關聯關系。主表和子表使用相同的分片策略。表在上面已經上傳過就是表t_order和t_order_item
綁定表將使用主表的分片策略。
# 分庫算法 t_order 多庫分表(er表) spring.shardingsphere.sharding.tables.t_order.databaseStrategy.inline.shardingColumn=order_id spring.shardingsphere.sharding.tables.t_order.databaseStrategy.inline.algorithm-expression=ds${order_id % 2} spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order # 分庫算法 t_order_item 多庫分表(er表) spring.shardingsphere.sharding.tables.t_order_item.databaseStrategy.inline.shardingColumn=order_id spring.shardingsphere.sharding.tables.t_order_item.databaseStrategy.inline.algorithm-expression=ds${order_id % 2} spring.shardingsphere.sharding.tables.t_order_item.actual-data-nodes=ds$->{0..1}.t_order_item # 綁定表規則列表,防止關聯查詢出現笛卡爾積 spring.shardingsphere.sharding.binding-tables[0]=t_order,t_order_item
除了定義分庫和分表算法之外,我們還需要多定義一個 binding-tables。綁定表不使用分片鍵查詢時,會出現笛卡爾積。至於笛卡爾積的概念相信看這篇幅文章的朋友都會很清楚,下圖一張是不使用分片鍵的效果,另一張是使用后的效果;看過我前面mycat的朋友應該很清楚了mycat 不支持這種二維的路由,要么是分庫,要么是分表
配置文件搞定了,下面來寫個代碼測試下
測試類
@RunWith(SpringRunner.class) @SpringBootTest public class OrderShardingTest { @Resource OrderService orderService; @Test public void insert(){ orderService.insert(); } @Test public void select(){ Order order1 = orderService.getOrderInfoByOrderId(1); System.out.println("------order1:"+order1); Order order2 = orderService.getOrderInfoByOrderId(2); System.out.println("------order2:"+order2); } }
public class Order implements Serializable { private Integer orderId; private Integer userId; public Integer getOrderId() { return orderId; } public void setOrderId(Integer orderId) { this.orderId = orderId; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } @Override public String toString() { return " -------- Order{" + "orderId=" + orderId + ", userId=" + userId + '}'; } }
@Service public class OrderService { @Resource OrderMapper orderMapper; public static Long orderId = 1L; public static Long userId = 1L; public void insert() { for (int i = 1; i <= 100; i++) { Order order = new Order(); order.setOrderId(i); order.setUserId(i); orderId++; userId++; orderMapper.insert(order); } } public Order getOrderInfoByOrderId(Integer id){ return orderMapper.selectByPrimaryKey(id); } }
public interface OrderMapper { int insert(Order record); Order selectByPrimaryKey(Integer orderId); }
<resultMap id="BaseResultMap" type="com.ghy.springbootsharding.entity.Order"> <id column="order_id" jdbcType="INTEGER" property="orderId" /> <result column="user_id" jdbcType="INTEGER" property="userId" /> </resultMap> <sql id="Base_Column_List"> order_id, user_id </sql> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from t_order where order_id = #{orderId,jdbcType=INTEGER} </select> <insert id="insert" parameterType="com.ghy.springbootsharding.entity.Order"> insert into t_order (order_id, user_id) values (#{orderId,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}) </insert>
測試類
@RunWith(SpringRunner.class) @SpringBootTestpublic class OrderItemShardingTest { @Resource OrderItemService orderItemService; @Test public void insert(){ orderItemService.insert(); } @Test public void select(){ OrderItem orderItem1 = orderItemService.getOrderItemByItemId(1); System.out.println("------orderItem1:"+orderItem1); OrderItem orderItem2 = orderItemService.getOrderItemByItemId(2); System.out.println("------orderItem2:"+orderItem2); } }
public class OrderItem { private Integer itemId; private Integer orderId; private Integer userId; public Integer getItemId() { return itemId; } public void setItemId(Integer itemId) { this.itemId = itemId; } public Integer getOrderId() { return orderId; } public void setOrderId(Integer orderId) { this.orderId = orderId; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } @Override public String toString() { return " -------- OrderItem{" + "itemId=" + itemId + ", orderId=" + orderId + ", userId=" + userId + '}'; } }
@Service public class OrderItemService { @Resource OrderItemMapper orderItemMapper; public void insert() { for (int i = 1; i <= 100; i++) { OrderItem orderItem = new OrderItem(); orderItem.setItemId(i); orderItem.setOrderId(i); orderItem.setUserId(i); orderItemMapper.insert(orderItem); } } public OrderItem getOrderItemByItemId(Integer id){ return orderItemMapper.selectByPrimaryKey(id); } }
public interface OrderItemMapper { int insert(OrderItem record); OrderItem selectByPrimaryKey(Integer itemId); }
<resultMap id="BaseResultMap" type="com.ghy.springbootsharding.entity.OrderItem"> <id column="item_id" jdbcType="INTEGER" property="itemId" /> <result column="order_id" jdbcType="INTEGER" property="orderId" /> <result column="user_id" jdbcType="INTEGER" property="userId" /> </resultMap> <sql id="Base_Column_List"> item_id, order_id, user_id </sql> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from t_order_item where item_id = #{itemId,jdbcType=INTEGER} </select> <insert id="insert" parameterType="com.ghy.springbootsharding.entity.OrderItem"> insert into t_order_item (item_id, order_id, user_id ) values (#{itemId,jdbcType=INTEGER}, #{orderId,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER} ) </insert>
先插入主表的數據,再插入子表的數據。
然后可以用我上圖片的關連表查詢查的玩玩
2.4.3、 廣播表
最后一種是廣播表,也就是需要在所有節點上同步操作的數據表。
spring.shardingsphere.sharding.broadcast-tables=t_config
一樣配置完成后測試
@RunWith(SpringRunner.class) @SpringBootTest @MapperScan(basePackages = "com.ghy.springbootsharding.mapper") public class ConfigShardingTest { @Resource ConfigService configService; @Test public void insert(){ configService.insert(); } @Test public void update(){ configService.update(1); } @Test public void select(){ Config config1 = configService.geConfigById(1); System.out.println("------config1:"+config1); Config config2 = configService.geConfigById(2); System.out.println("------config2:"+config2); } }
public class Config { private Integer configId; private String paraName; private String paraValue; private String paraDesc; public Integer getConfigId() { return configId; } public void setConfigId(Integer configId) { this.configId = configId; } public String getParaName() { return paraName; } public void setParaName(String paraName) { this.paraName = paraName == null ? null : paraName.trim(); } public String getParaValue() { return paraValue; } public void setParaValue(String paraValue) { this.paraValue = paraValue == null ? null : paraValue.trim(); } public String getParaDesc() { return paraDesc; } public void setParaDesc(String paraDesc) { this.paraDesc = paraDesc == null ? null : paraDesc.trim(); } @Override public String toString() { return " -------- Config {" + "configId=" + configId + ", paraName='" + paraName + '\'' + ", paraValue='" + paraValue + '\'' + ", paraDesc='" + paraDesc + '\'' + '}'; }
@Service public class ConfigService { @Resource ConfigMapper configMapper; public static Long configId = 1L; public void insert() { for (int i = 1; i <= 10; i++) { Config config = new Config(); config.setConfigId(i); config.setParaName("name"+i); config.setParaValue("value"+i); config.setParaDesc("desc"+i); configId++; configMapper.insert(config); } } public void update(Integer configId) { Config config = configMapper.selectByPrimaryKey(configId); config.setParaDesc("after modified. 2673-666"); configMapper.updateByPrimaryKey(config); } public Config geConfigById(Integer id){ return configMapper.selectByPrimaryKey(id); } }
public interface ConfigMapper { int insert(Config config); Config selectByPrimaryKey(Integer configId); int updateByPrimaryKey(Config record); }
<resultMap id="BaseResultMap" type="com.ghy.springbootsharding.entity.Config"> <id column="config_id" jdbcType="INTEGER" property="configId" /> <result column="para_name" jdbcType="VARCHAR" property="paraName" /> <result column="para_value" jdbcType="VARCHAR" property="paraValue" /> <result column="para_desc" jdbcType="VARCHAR" property="paraDesc" /> </resultMap> <sql id="Base_Column_List"> config_id, para_name, para_value, para_desc </sql> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from t_config where config_id = #{configId,jdbcType=INTEGER} </select> <insert id="insert" parameterType="com.ghy.springbootsharding.entity.Config"> insert into t_config (config_id, para_name, para_value, para_desc) values (#{configId,jdbcType=INTEGER}, #{paraName,jdbcType=VARCHAR}, #{paraValue,jdbcType=VARCHAR}, #{paraDesc,jdbcType=VARCHAR}) </insert> <update id="updateByPrimaryKey" parameterType="com.ghy.springbootsharding.entity.Config"> update t_config set para_name = #{paraName,jdbcType=VARCHAR}, para_value = #{paraValue,jdbcType=VARCHAR}, para_desc = #{paraDesc,jdbcType=VARCHAR} where config_id = #{configId,jdbcType=INTEGER} </update>
2.4.4 、讀寫分離
讀寫分離的API實現前面已經寫過了,在springboot里面,讀寫分離變的更加簡單只用簡單的幾個配置
master-slave-rules: #這里配置讀寫分離的時候一定要記得添加主庫的數據源名稱 這里為master0
master0: #指定master0為主庫,slave0為它的從庫
master-data-source-name: master0
slave-data-source-names: slave0
master1: #指定master1為主庫,slave1為它的從庫
master-data-source-name: master1
slave-data-source-names: slave1
然后配置數據源
# 數據源 shardingsphere: datasource: names: master0,master1,slave0,slave1 #數據庫db master0: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.2.121:3306/ds0 username: root password: root master1: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.2.121:3306/ds1 username: root password: root slave0: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.2.128:3306/ds0 username: root password: root slave1: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.2.128:3306/ds1 username: root password: root
就搞定了