3

SpringBoot3分库分表 - 知了一笑

 9 months ago
source link: https://www.cnblogs.com/cicada-smile/p/17622090.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

标签:ShardingSphere5.分库.分表;

分库分表的设计和实现方式,在之前的内容中总结过很多,本文基于SpringBoot3ShardingSphere5框架实现数据分库分表的能力;

不得不提ShardingSphere5文档中描述的两个基本概念:

1691717-20230810223457889-963165329.png

垂直分片

按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用。在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将压力分散至不同的数据库。

水平分片

水平分片又称为横向拆分。 相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。

下面从案例实践中,看看ShardingSphere5框架是如何实现分库分表的原理;

二、工程搭建

1、工程结构

1691717-20230810223502733-847102565.png

2、依赖管理

这里只看两个核心组件的依赖:shardingsphere-jdbc组件是5.2.1版本,mybatis组件是3.5.13版本,在依赖管理中还涉及MySQL和分页等,并且需要添加很多排除配置,具体见源码;

<!-- Mybatis组件 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>${mybatis.version}</version>
</dependency>

<!-- ShardingSphere分库分表 -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>

三、配置详解

1、配置文件

此处只展示分库分表的相关配值,默认数据源使用db_master库,注意tb_order库表路由的策略和分片算法的关联关系,其他工程配置详见源码仓库;

spring:
  # 分库分表配置
  shardingsphere:
    datasource:
      # 默认数据源
      sharding:
        default-data-source-name: db_master
      names: db_master,db_0,db_1
      db_master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/shard_db
        username: root
        password: 123456
      db_0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/shard_db_0
        username: root
        password: 123456
      db_1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/shard_db_1
        username: root
        password: 123456
    rules:
      sharding:
        tables:
          # tb_order逻辑
          tb_order:
            actual-data-nodes: db_${0..1}.tb_order_${0..2}
            # tb_order库路由
            database-strategy:
              standard:
                sharding-column: order_id
                sharding-algorithm-name: database_inline
            # tb_order表路由
            table-strategy:
              standard:
                sharding-column: order_id
                sharding-algorithm-name: table_inline
        sharding-algorithms:
          # tb_order库路由算法
          database_inline:
            type: INLINE
            props:
              algorithm-expression: db_${order_id % 2}
          # tb_order表路由算法
          table_inline:
            type: INLINE
            props:
              algorithm-expression: tb_order_${order_id % 3}
    props:
      sql-show: true
      sql-comment-parse-enabled: true

2、配置原理

在配置中需要管理三个数据源,shard_db默认库,在操作不涉及需要路由的表时默认使用该数据源,shard_db_0shard_db_1tb_order逻辑表的路由库;

1691717-20230810223507824-891612061.png

逻辑表tb_order整体使用两个数据库,每个库建3张结构相同相同的表,在操作tb_order数据时,会根据order_id字段值定位数据所属的分片节点;

  • 库路由db_${0..1}采用db_${order_id%2}的算法;
  • 表路由tb_order_${0..2}采用tb_order_${order_id%3}的算法;

四、测试案例

1、主库操作

基于Mybatis持久层框架,实现对shard_db默认库的数据操作,注意控制台的日志打印,可以看到一系列解析逻辑以及库表节点的定位,分页查询使用PageHelper组件即可;

public class MasterTest {
    @Autowired
    private BuyerMapper buyerMapper ;
    @Autowired
    private SellerMapper sellerMapper ;
    @Test
    public void testBuyerQuery (){
        // 主键查询
        Buyer buyer = buyerMapper.selectByPrimaryKey(1) ;
        System.out.println(buyer.getId()+";"+buyer.getBuyerName());
    }
    @Test
    public void testBuyerInsert (){
        // 新增数据
        Buyer buyer = new Buyer() ;
        buyer.setBuyerName("买家Three");
        System.out.println(buyerMapper.insert(buyer));
    }
    @Test
    public void testBuyerUpdate (){
        // 更新数据
        Buyer buyer = buyerMapper.selectByPrimaryKey(3) ;
        if (buyer != null){
            buyer.setBuyerName("Three买家");
            System.out.println(buyerMapper.updateByPrimaryKey(buyer));
        }
    }
    @Test
    public void testSellerPage (){
        // 1、设置分页和查询条件
        PageHelper.startPage(2,2) ;
        SellerExample sellerExample = new SellerExample() ;
        sellerExample.setOrderByClause("id asc");
        // 2、查询数据
        List<Seller> sellerList = sellerMapper.selectByExample(sellerExample) ;
        // 3、构建分页实体对象
        PageInfo<Seller> pageInfo = new PageInfo<>(sellerList) ;
        System.out.println(pageInfo);
    }
}

2、分库操作

在对tb_order表执行增删改查时,会根据order_id的字段值计算库表的路由节点,注意分页时会查询所有的分库和分表,然后汇总查询的结果;

public class ShardTest {
    @Autowired
    private OrderMapper orderMapper ;
    /**
     * 写入100条数据
     */
    @Test
    public void testOrderInsert (){
        for (int i=1 ; i<= 100 ; i++){
            Order order = new Order(i,i%3+1,i%3+1) ;
            // orderMapper.insert(order) ;
        }
    }
    @Test
    public void testOrderQuery (){
        Order order = orderMapper.selectByPrimaryKey(5) ;
        System.out.println(order);
    }
    @Test
    public void testOrderUpdate (){
        Order order = orderMapper.selectByPrimaryKey(100) ;
        if (order != null){
            // 原数据:买家和卖家ID都是2
            order.setBuyerId(1);
            order.setSellerId(3);
            orderMapper.updateByPrimaryKey(order) ;
        }
    }

    @Test
    public void testOrderPage (){
        // 1、设置分页和查询条件
        PageHelper.startPage(1,10) ;
        OrderExample orderExample = new OrderExample() ;
        orderExample.createCriteria().andBuyerIdEqualTo(2).andSellerIdEqualTo(2);
        orderExample.setOrderByClause("order_id desc");
        // 2、查询数据
        List<Order> orderList = orderMapper.selectByExample(orderExample) ;
        // 3、构建分页实体对象
        PageInfo<Order> pageInfo = new PageInfo<>(orderList) ;
        System.out.println(pageInfo);
    }
}

3、综合查询

编写一个订单详情查询接口,同时使用三个库构建数据结构;如果是基于列表数据的检索,比较常规做法的是构建ES索引结构,如果没有搜索的需求,可以在订单表分页查询后去拼接其他结构;

@RestController
public class OrderController {

    @Resource
    private BuyerMapper buyerMapper ;
    @Resource
    private SellerMapper sellerMapper ;
    @Resource
    private OrderMapper orderMapper ;

    /**
     * 查询订单详情
     */
    @GetMapping("/order/info/{orderId}")
    public Map<String,Object> orderInfo (@PathVariable Integer orderId){
        Map<String,Object> orderMap = new HashMap<>() ;
        Order order = orderMapper.selectByPrimaryKey(orderId) ;
        if (order != null){
            orderMap.put("order",order) ;
            orderMap.put("buyer",buyerMapper.selectByPrimaryKey(order.getBuyerId())) ;
            orderMap.put("seller",sellerMapper.selectByPrimaryKey(order.getSellerId())) ;
        }
        return orderMap ;
    }
}

查看SQL语句

db_master ::: select id, buyer_name from tb_buyer where id = ? ::: [1]
db_master ::: select id, seller_name from tb_seller where id = ? ::: [3]
db_0 ::: select order_id, seller_id, buyer_id from tb_order_1 where order_id = ? ::: [100]

五、参考源码

文档仓库:
https://gitee.com/cicadasmile/butte-java-note

源码仓库:
https://gitee.com/cicadasmile/butte-spring-parent

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK