springboot 整合 Shardingsphere 4.0 分庫分表

最近Shardingsphere在Apache Software Foundation 簡稱ASF 畢業成爲Apache頂級項目,也是目前ASF收個分佈式數據庫中間件項目,未來可期啊,今天我們就搭建一下springboot整合Shardingsphere4.0版本。

依賴:

  • jdk1.8
  • maven3.6.3
  • mybatis plus
  • mysql8.0
  • Shardingsphere 4.0

數據庫的結構:

–|
|- ds0
| |- t_order0
| |- t_order1
|
|- ds1
|- t_order0
|- t_order1

創建數據庫

數據庫ds0

CREATE DATABASE IF NOT EXISTS `ds0` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
USE `ds0`;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_order0
-- ----------------------------
DROP TABLE IF EXISTS `t_order0`;
CREATE TABLE `t_order0`  (
  `order_id` bigint(0) NOT NULL COMMENT '訂單號(主鍵)',
  `order_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '訂單名稱',
  `order_status` int(0) NULL DEFAULT NULL COMMENT '訂單狀態',
  `user_id` bigint(0) NOT NULL COMMENT '用戶id',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_order1
-- ----------------------------
DROP TABLE IF EXISTS `t_order1`;
CREATE TABLE `t_order1`  (
  `order_id` bigint(0) NOT NULL COMMENT '訂單號(主鍵)',
  `order_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '訂單名稱',
  `order_status` int(0) NULL DEFAULT NULL COMMENT '訂單狀態',
  `user_id` bigint(0) NOT NULL COMMENT '用戶id',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

數據庫ds1

CREATE DATABASE IF NOT EXISTS `ds1` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
USE `ds1`;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_order0
-- ----------------------------
DROP TABLE IF EXISTS `t_order0`;
CREATE TABLE `t_order0`  (
  `order_id` bigint(0) NOT NULL COMMENT '訂單號(主鍵)',
  `order_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '訂單名稱',
  `order_status` int(0) NULL DEFAULT NULL COMMENT '訂單狀態',
  `user_id` bigint(0) NOT NULL COMMENT '用戶id',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_order1
-- ----------------------------
DROP TABLE IF EXISTS `t_order1`;
CREATE TABLE `t_order1`  (
  `order_id` bigint(0) NOT NULL COMMENT '訂單號(主鍵)',
  `order_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '訂單名稱',
  `order_status` int(0) NULL DEFAULT NULL COMMENT '訂單狀態',
  `user_id` bigint(0) NOT NULL COMMENT '用戶id',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.winterchen</groupId>
    <artifactId>shardingsphere-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shardingsphere-demo</name>
    <description>shardingsphere demo</description>

    <properties>
        <java.version>1.8</java.version>
        <sharding-sphere.version>4.0.1</sharding-sphere.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mysql,根據自己數據庫版本進行相關調整,不然會報錯-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
            <scope>runtime</scope>
        </dependency>
        <!--Mybatis-Plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.1</version>
        </dependency>
        <!-- for spring boot -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>${sharding-sphere.version}</version>
        </dependency>

        <!-- for spring namespace -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-namespace</artifactId>
            <version>${sharding-sphere.version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

注意:數據庫驅動版本,請根據自己數據庫版本進行相關調整,不然會報錯

項目配置

application.properties

server.port=8098

# 數據源 ds0,ds1
spring.shardingsphere.datasource.names=ds0,ds1
# 第一個數據庫
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://192.168.133.134:3306/ds0?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&verifyServerCertificate=false&autoReconnct=true&autoReconnectForPools=true&allowMultiQueries=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=root

# 第二個數據庫
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://192.168.133.134:3306/ds1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&verifyServerCertificate=false&autoReconnct=true&autoReconnectForPools=true&allowMultiQueries=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=root

# 水平拆分的數據庫(表) 配置分庫 + 分表策略 行表達式分片策略
# 分庫策略
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 2}


# 分表策略 其中t_order爲邏輯表 分表主要取決於order_id行
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order$->{0..1}
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id
spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id
spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE

# 分片算法表達式
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order$->{order_id % 2}


# 打印執行的數據庫以及語句
spring.shardingsphere.props.sql.show=true
spring.main.allow-bean-definition-overriding=true


# mybatis-plus
mybatis-plus.mapper-locations=classpath:/mapper/*.xml
mybatis-plus.configuration.jdbc-type-for-null='null'


注意:數據庫連接配置、mybatis-plus掃包路徑,需要根據實際情況進行改變

以上的配置實現了分庫分表,一下幾點概念需要明白:

邏輯表

水平拆分的數據庫(表)的相同邏輯和數據結構表的總稱。例:訂單數據根據主鍵尾數拆分爲10張表,分別是t_order0到t_order9,他們的邏輯表名爲t_order。

真實表

在分片的數據庫中真實存在的物理表。即上個示例中的t_order0到t_order9。

數據節點

數據分片的最小單元。由數據源名稱和數據表組成,例:ds0.t_order0。

分片鍵

用於分片的數據庫字段,是將數據庫(表)水平拆分的關鍵字段。例:將訂單表中的訂單主鍵的尾數取模分片,則訂單主鍵爲分片字段。 SQL中如果無分片字段,將執行全路由,性能較差。 除了對單分片字段的支持,ShardingSphere也支持根據多個字段進行分片。

分片算法

通過分片算法將數據分片,支持通過=、>=、<=、>、<、BETWEEN和IN分片。分片算法需要應用方開發者自行實現,可實現的靈活度非常高。

目前提供4種分片算法。由於分片算法和業務實現緊密相關,因此並未提供內置分片算法,而是通過分片策略將各種場景提煉出來,提供更高層級的抽象,並提供接口讓應用開發者自行實現分片算法。

  • 精確分片算法

對應PreciseShardingAlgorithm,用於處理使用單一鍵作爲分片鍵的=與IN進行分片的場景。需要配合StandardShardingStrategy使用。

  • 範圍分片算法

對應RangeShardingAlgorithm,用於處理使用單一鍵作爲分片鍵的BETWEEN AND、>、<、>=、<=進行分片的場景。需要配合StandardShardingStrategy使用。

  • 複合分片算法

對應ComplexKeysShardingAlgorithm,用於處理使用多鍵作爲分片鍵進行分片的場景,包含多個分片鍵的邏輯較複雜,需要應用開發者自行處理其中的複雜度。需要配合ComplexShardingStrategy使用。

  • Hint分片算法

對應HintShardingAlgorithm,用於處理使用Hint行分片的場景。需要配合HintShardingStrategy使用。

分片策略

包含分片鍵和分片算法,由於分片算法的獨立性,將其獨立抽離。真正可用於分片操作的是分片鍵 + 分片算法,也就是分片策略。目前提供5種分片策略。

  • 標準分片策略

對應StandardShardingStrategy。提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片算法。PreciseShardingAlgorithm是必選的,用於處理=和IN的分片。RangeShardingAlgorithm是可選的,用於處理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫路由處理。

  • 複合分片策略

對應ComplexShardingStrategy。複合分片策略。提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片鍵,由於多分片鍵之間的關係複雜,因此並未進行過多的封裝,而是直接將分片鍵值組合以及分片操作符透傳至分片算法,完全由應用開發者實現,提供最大的靈活度。

  • 行表達式分片策略

對應InlineShardingStrategy。使用Groovy的表達式,提供對SQL語句中的=和IN的分片操作支持,只支持單分片鍵。對於簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開發,如: t_user_$->{u_id % 8} 表示t_user表根據u_id模8,而分成8張表,表名稱爲t_user_0到t_user_7。

  • Hint分片策略

對應HintShardingStrategy。通過Hint指定分片值而非從SQL中提取分片值的方式進行分片的策略。

  • 不分片策略

對應NoneShardingStrategy。不分片的策略。

SQL Hint

對於分片字段非SQL決定,而由其他外置條件決定的場景,可使用SQL Hint靈活的注入分片字段。例:內部系統,按照員工登錄主鍵分庫,而數據庫中並無此字段。SQL Hint支持通過Java API和SQL註釋(待實現)兩種方式使用。

自增主鍵生成策略

通過在客戶端生成自增主鍵替換以數據庫原生自增主鍵的方式,做到分佈式主鍵無重複。 採用UUID.randomUUID()的方式產生分佈式主鍵。或者 SNOWFLAKE

mybatis-plus

mybatis-plus極大的提高了開發效率,簡單的配置和使用mybatis-plus

配置mybatis-plus的分頁

package com.winterchen.shardingspheredemo.configuration;


@EnableTransactionManagement
@Configuration
@MapperScan("com.winterchen.shardingspheredemo.dao")
public class MybatisPlusConfig {

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 設置請求的頁面大於最大頁後操作, true調回到首頁,false 繼續請求  默認false
        // paginationInterceptor.setOverflow(false);
        // 設置最大單頁限制數量,默認 500 條,-1 不受限制
        // paginationInterceptor.setLimit(500);
        // 開啓 count 的 join 優化,只針對部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize());
        return paginationInterceptor;
    }
}

配置自定義的通用mapper

package com.winterchen.shardingspheredemo.configuration;

import java.util.Map;

public interface MyBaseMapper<T> extends BaseMapper<T> {

    IPage<T> selectAllByCondition(Page<?> page, @Param("condition")Map<String, Object> condition);

    int deleteById(@Param("condition")Map<String, Object> condition);

}

代碼

實體類

package com.winterchen.shardingspheredemo.model;


@TableName("t_order")
public class OrderInfo {

    @TableId(value = "order_id")
    private Long orderId;

    @TableField(value = "order_name")
    private String orderName;

    @TableField(value = "order_status")
    private Integer orderStatus;

    @TableField(value = "user_id")
    private Long userId;

    //省略get/set
}

Mapper

mapper

package com.winterchen.shardingspheredemo.dao;


public interface OrderMapper extends MyBaseMapper<OrderInfo> {

    IPage<OrderInfo> selectAllByCondition(Page<?> page, @Param("condition") Map<String, Object> condition);

    int deleteById(@Param("condition")Map<String, Object> condition);

}

mapper xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.winterchen.shardingspheredemo.dao.OrderMapper">
    
    <sql id="Base_Column_List">
        order_id, order_name, order_status, user_id
    </sql>
    
    <select id="selectAllByCondition" resultType="com.winterchen.shardingspheredemo.model.OrderInfo">
        select
            <include refid="Base_Column_List"/>
        from
           t_order
        <where>
            <if test="condition.userId != null">
                and user_id = #{condition.userId, jdbcType=BIGINT}
            </if>
            <if test="condition.orderId != null">
                and order_id = #{condition.orderId, jdbcType=BIGINT}
            </if>
            <if test="condition.orderName != null and condition.orderName != ''">
                and order_name like concat('%',#{condition.orderName,jdbcType=VARCHAR} ,'%')
            </if>
            <if test="condition.orderStatus != null">
                and order_status = #{condition.orderStatus, jdbcType=INTEGER}
            </if>
        </where>
    </select>

    <delete id="deleteById">
        delete from t_order where order_id = #{condition.orderId, jdbcType=BIGINT} and user_id = #{condition.userId, jdbcType=BIGINT}
    </delete>

</mapper>

service

package com.winterchen.shardingspheredemo.service;


public interface OrderService extends IService<OrderInfo> {

    @Override
    boolean save(OrderInfo entity);

    @Override
    boolean removeById(Serializable id);

    @Override
    boolean updateById(OrderInfo entity);

    IPage<OrderInfo> pageOrderInfos(Page<?> page, OrderInfo orderInfo);

}

package com.winterchen.shardingspheredemo.service.impl;


@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderInfo> implements OrderService {


    @Override
    public boolean save(OrderInfo entity) {
        return super.save(entity);
    }

    @Override
    public boolean removeById(Serializable id) {
        return super.removeById(id);
    }

    @Override
    public boolean updateById(OrderInfo entity) {
        return super.updateById(entity);
    }


    @Override
    public IPage<OrderInfo> pageOrderInfos(Page<?> page, OrderInfo orderInfo) {
        Map<String, Object> map = new HashMap<>();
        map.put("userId", orderInfo.getUserId());
        map.put("orderId", orderInfo.getOrderId());
        map.put("orderName", orderInfo.getOrderName());
        map.put("orderStatus", orderInfo.getOrderStatus());
        return super.baseMapper.selectAllByCondition(page, map);
    }
}

controller

package com.winterchen.shardingspheredemo.controller;


@RestController
@RequestMapping("/api/v1/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("")
    public boolean save(@RequestBody OrderInfo orderInfo) {
        return orderService.save(orderInfo);
    }

    @DeleteMapping("/{orderId}")
    public boolean deleteById(
            @PathVariable("orderId")
            Long id) {
        return orderService.removeById(id);
    }

    @PutMapping("/{orderId}")
    public boolean updateById(
            @PathVariable("orderId")
            Long orderId,
            @RequestBody OrderInfo orderInfo) {
        orderInfo.setOrderId(orderId);
        return orderService.updateById(orderInfo);
    }

    @GetMapping("/list")
    public IPage page(
            @RequestParam(name = "pageNum", required = false, defaultValue = "1")
                    Integer pageNum,
            @RequestParam(name = "pageSize", required = false, defaultValue = "10")
                    Integer pageSize,
            @RequestParam(name = "orderId", required = false)
            Long orderId,
            @RequestParam(name = "orderStatus", required = false)
            Integer orderStatus,
            @RequestParam(name = "orderName", required = false)
            String orderName) {
        Page<OrderInfo> page = new Page<>(pageNum,pageSize);
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOrderId(orderId);
        orderInfo.setOrderName(orderName);
        orderInfo.setOrderStatus(orderStatus);
        return orderService.pageOrderInfos(page, orderInfo);
    }
}

啓動類

package com.winterchen.shardingspheredemo;



@SpringBootApplication
public class ShardingsphereDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShardingsphereDemoApplication.class, args);
    }

}

以上就整合完畢了,可以使用postMan進行測試,測試截圖就不展示了,後面會介紹關於讀寫分離的相關介紹,如果需要查看詳細的介紹可以參考官方文檔。

相關資源

  • 官方源碼地址:https://github.com/apache/shardingsphere
  • 官方文檔:https://shardingsphere.apache.org/document/current/cn/overview/
  • mybatis-plus官方文檔:https://mp.baomidou.com/guide/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章