分布式数据库中间件-Sharding-JDBC

news2024/12/25 1:06:19

文章目录

  • Sharding-JDBC
    • Sharding-JDBC介绍
    • Sharding-JDBC的作用
    • 什么是分库分表
    • 分库分表的方式
    • 分库分表带来的问题
      • 事务一致性问题
      • 跨节点关联查询
      • 跨节点分页、排序函数
      • 主键重复
    • Sharding-JDBC 入门(水平分表)
      • 需求说明
      • 环境搭建
      • 编写代码
      • 流程分析
      • 其他配置方式
      • 概念名词解释
      • **执行原理**
    • 水平分库
      • 执行流程分析
    • 垂直分库(补充)
      • 配置分片策略
      • 用户数据操作

Sharding-JDBC

Sharding-JDBC介绍

Sharding-JDBC是当当网研发的开源分布式数据库中间件,定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架,从3.0开始Sharding-JDBC被包含在Sharding-Sphere中。

ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景

在这里插入图片描述

Sharding-JDBC的作用

Sharding-JDBC的核心功能为数据分片读写分离,通过Sharding-JDBC,应用可以透明的使用Jdbc访问已经分库分表、读写分离的多个数据源,而不用关心数据源的数量以及数据如何分布。

  • 适用于任何基于JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
  • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
  • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。

官网地址:概览 :: ShardingSphere (apache.org)

在这里插入图片描述

什么是分库分表

学茶网是一座茶艺殿堂,融合了垂直电商平台的精髓。在这里,用户不仅可以享受茶道百科的精华,还能尽情选购各类珍稀茶叶。

在这里插入图片描述

尽管初创时用户规模有限,但随着用户的增加以及公司业务快速发展,我们的内容也与日俱增。然而,随着数据库中的数据量猛增,而关系型数据库本身的单机存储容量、连接数、处理能力有限,产生系统瓶颈,导致访问性能下降严重,在多维度查询下,即使添加从库、优化索引也无法很好的提升单表数据量过大所带来的性能问题。

解决方案一:通过提升服务器硬件能力来提高数据处理能力,比如增加存储容量、CPU等这种方案成本很高,并且如果瓶颈在MySQL本身那么提高硬件也是有很有必要的。

解决方案二:把数据分散在不同的数据库中,使得单一数据库的数据量变小来缓解单一数据库的性能问题,从而达到提升数据库性能的目的,如下图:将学茶的数据库拆分为若干独立的数据库,并且对于大表也拆分为若干小表,通过这种数据库拆分的方法来解决数据库的性能问题。

在这里插入图片描述

分库分表就是为了解决由于数据量过大而导致数据库性能降低的问题,将原来独立的数据库拆分成若干数据库组成 ,将数据大表拆分成若干数据表组成,使得单一数据库、单一数据表的数据量变小,从而达到提升数据库性能的目的。

分库分表的方式

分库分表包括分库和分表两个部分,在生产中通常包括:垂直分库、水平分库、垂直分表、水平分表四种方式

垂直分表:将一个表按照字段分成多表,每个表存储其中一部分字段。

用户在浏览商品列表时,茶叶信息中茶叶名称、茶叶图片、茶叶价格等其他字段数据访问频次较高。

在这里插入图片描述

只有对某茶叶感兴趣时才会查看该茶叶的详细描述。因此,茶叶信息中茶叶详情信息等字段访问频次较低,且该字段存储占用空间较大,访问单个数据IO时间较长

在这里插入图片描述

由于这两种数据的特性不一样,因此考虑将茶叶信息表拆分如下:
将访问频次低的商品描述信息单独存放在一张表中,访问频次较高的商品基本信息单独放在一张表中

在这里插入图片描述

通过垂直分表所带来的提升是:

  • 为了避免IO争抢并减少锁表的几率,查看详情的用户与商品信息浏览互不影响
  • 充分发挥热门数据的操作效率,商品信息的操作的高效率不会被商品描述的低效率所拖累。
为什么大字段IO效率低:
1.由于数据量本身大,需要更长的读取时间;
2.跨页,页是数据库存储单位,很多查找及定位操作都是以页为单位,单页内的数据行越多数据库整体性能越好,而大字段占用空间大,单页内存储行	 数少,因此IO效率较低。
3.数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加载更多的数据,命中率更高,减少了磁盘IO,从而提升   了数据库性能。

什么是锁表:
锁表是数据库中一种机制,用于确保在对数据库进行读取或写入操作时数据的一致性和完整性。当一个事务在对某张表进行修改时,系统会将该表锁定,阻止其他事务对其进行修改,直到该事务完成操作释放锁。这有助于避免数据冲突和并发问题,确保数据操作的正确性。

垂直分库:按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用

通过垂直分表性能得到了一定程度的提升,但是还没有达到要求,并且磁盘空间也快不够了,因为数据还是始终限制在一台服务器,库内垂直分表只解决了单一表数据量过大的问题,但没有将表分布到不同的服务器上,因此每个表还是竞争同一个物理机的CPU、内存、网络IO、磁盘。

在这里插入图片描述

它带来的提升是:

  • 解决业务层面的耦合,业务清晰
  • 能对不同业务的数据进行分级管理、维护、监控、扩展等
  • 高并发场景下,垂直分库一定程度的提升IO、数据库连接数、降低单机硬件资源的瓶颈
注意:垂直分库通过将表按业务分类,然后分布在不同数据库,并且可以将这些数据库部署在不同服务器上,从而
达到多个服务器共同分摊压力的效果,但是依然没有解决单表数据量过大的问题。

水平分库:把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。

经过垂直分库后,数据库性能问题得到一定程度的解决,但是随着业务量的增长,tmall-server-mall(商品库)单库存储数据已经超出预估。粗略估计,目前商品数量得往1500w+上预估,并且tmall-server-mall(商品库)属于访问非常频繁的资源,单台服务器已经无法支撑。

从业务角度分析,目前情况已经无法再次垂直分库

通过水平分库,将类别ID为单数和类别ID为双数的商品信息分别放到两个库中

具体规则如下:

要操作某条数据,先分析这条数据所属的类别ID。如果类别ID为双数,将此操作映射至tmall-server-mall1(商品库1);如果类别ID为单数,将操作映射至tmall-server-mall2(商品库2)。此操作要访问数据库名称的表达式为tmall-server-mall【类别ID%2+1】

在这里插入图片描述

它带来的提升是:

  • 解决了单库大数据,高并发的性能瓶颈。

  • 提高了系统的稳定性及可用性。稳定性体现在IO冲突减少,锁定减少,可用性指某个库出问题,部分可用

对比:垂直分库是把不同表拆到不同数据库中,它是对数据行的拆分,不影响表结构

**水平分表:**是在同一个数据库内,把同一个表的数据按一定规则拆到多个表中

通过水平分库和垂直分库可以解决上述部分问题,但是如果单表数据量依旧较为庞大,那么再次进行水平分库会过多的增加数据库实例,提升数据库运维的难度和压力,可以按照水平分库的思路对把tmall-server-mall X(商品库)内的表也可以进行水平拆分,其目的也是为解决单表数据量大的问题

在这里插入图片描述

与水平分库的思路类似,不过这次操作的目标是表,商品信息及商品描述被分成了两套表。如果商品ID为双数,将此操作映射至商品信息1表:如果商品ID为单数,将操作映射至商品信息2表。此操作要访问表名称的表达式为商品信息【商品ID%2+1】

它带来的提升是:

  • 解决了单库大数据,高并发的性能瓶颈。

  • 提高了系统的稳定性及可用性。稳定性体现在IO冲突减少,锁定减少,可用性指某个库出问题,部分可用

分库分表带来的问题

分库分表能有效的缓解了单机和单库带来的性能瓶颈和压力,突破网络IO、硬件资源、连接数的瓶颈,同时也带来了一些问题。

事务一致性问题

由于分库分表把数据分布在不同库甚至不同服务器,不可避免会带来分布式事务问题。

跨节点关联查询

在没有分库前,比如说我们要在文章中去嵌入一个商品信息,那么可以通过文章中的商品ID进行关联查询获取到商品详情,但是此时我们发现,商品和文章已经经过分库分表,不在同一个数据库,甚至部署在不同的服务器上,无法进行关联查询

可将原关联查询分为两次查询,第一次查询的结果集中找出关联数据id,然后根据id发起第二次请求得到关联数据,最后将获得到的数据进行拼装。

跨节点分页、排序函数

跨节点多库进行查询时,limit分页、order by排序等问题,就变得比较复杂了。需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序。

在这里插入图片描述

以上流程是取第一页的数据,性能影响不大,但由于商品信息的分布在各数据库的数据可能是随机的,如果是取第N页,需要将所有节点前N页数据都取出来合并,再进行整体的排序,操作效率可想而知。所以请求页数越大,系统的性能也会越差。
在使用Max、Min、Sum、Count之类的函数进行计算的时候,与排序分页同理,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回。

主键重复

在分库分表环境中,由于表中数据同时存在不同数据库中,主键值平时使用的自增长将无用武之地,某个分区数据库生成的ID无法保证全局唯一。因此需要单独设计全局主键,以避免跨库主键重复问题。

在这里插入图片描述

由于分库分表之后,数据被分散在不同的数据库、服务器。因此,对数据的操作也就无法通过常规方式完成,并且它还带来了一系列的问题。好在,这些问题不是所有都需要我们在应用层面上解决,市面上有很多中间件可供我们选择,其中Sharding-JDBC使用流行度较高。

Sharding-JDBC 入门(水平分表)

需求说明

人工创建两张表,mall_order_1mall_order_2,这两张表是订单表拆分后的表,通过Sharding-Jdbc向订单表插入数据,按照一定的分片规则,主键为偶数的进入mall_order_1,另一部分数据进入mall_order_2,通过Sharding-Jdbc 查询数据,根据 SQL语句的内容从mall_order_1mall_order_2查询数据。

环境搭建

主机名IP地址
mysql192.168.8.100/24
[root@mysql ~]# dnf -y install mysql-server mysql
[root@mysql ~]# systemctl start mysqld
[root@mysql ~]# systemctl enable mysqld
[root@mysql ~]# ss -nutlp | grep :3306
[root@mysql ~]# mysqladmin -uroot password "123qqq...A"			#修改root用户密码

创建数据库:tmall_server-mall

mysql> CREATE DATABASE `tmall_server-mall`;

授权root@'%'用户

mysql> CREATE USER root@'%' IDENTIFIED BY '123qqq...A';
mysql> GRANT ALL ON *.* TO root@'%';

tmall_server-mall数据库下创建mall_order_1表和mall_order_2

USE `tmall_server-mall`;
CREATE TABLE mall_order_1
(
    id               BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '数据ID',
    buyer_id         BIGINT UNSIGNED  DEFAULT 0 COMMENT '用户ID',
    buyer_username   VARCHAR(50)      DEFAULT '' COMMENT '用户名',
    order_no         VARCHAR(50)      DEFAULT '' COMMENT '订单编号',
    receiver_name    VARCHAR(32)      DEFAULT '' COMMENT '收货人',
    receiver_phone   VARCHAR(32)      DEFAULT '' COMMENT '收货电话',
    receiver_address VARCHAR(255)     DEFAULT '' COMMENT '收货地址',
    goods_num        INT UNSIGNED     DEFAULT 0 COMMENT '商品数量',
    total_price      DECIMAL(10, 2)   DEFAULT 0 COMMENT '商品销售总价',
    logistics_no     VARCHAR(50)      DEFAULT '' COMMENT '物流单号',
    pay_channel      INT UNSIGNED     DEFAULT 0 COMMENT '支付渠道:1=支付宝,2=微信',
    pay_method       INT UNSIGNED     DEFAULT 0 COMMENT '支付方式:1=在线支付,2=货到付款',
    order_state      TINYINT UNSIGNED DEFAULT 0 COMMENT '订单状态: 0=待支付,1=已支付,待发货, 2=已发货/待收货,3=确认收货/已完成,4=用户关闭,5=平台关闭(商家),6=系统调度关闭',
    gmt_pay          DATETIME         DEFAULT NULL COMMENT '支付时间',
    gmt_create       DATETIME         DEFAULT NULL COMMENT '数据创建时间',
    gmt_modified     DATETIME         DEFAULT NULL COMMENT '数据最后修改时间',
    PRIMARY KEY (id)
) COMMENT '商城-订单' CHARSET = utf8mb4;

CREATE TABLE mall_order_2
(
    id               BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '数据ID',
    buyer_id         BIGINT UNSIGNED  DEFAULT 0 COMMENT '用户ID',
    buyer_username   VARCHAR(50)      DEFAULT '' COMMENT '用户名',
    order_no         VARCHAR(50)      DEFAULT '' COMMENT '订单编号',
    receiver_name    VARCHAR(32)      DEFAULT '' COMMENT '收货人',
    receiver_phone   VARCHAR(32)      DEFAULT '' COMMENT '收货电话',
    receiver_address VARCHAR(255)     DEFAULT '' COMMENT '收货地址',
    goods_num        INT UNSIGNED     DEFAULT 0 COMMENT '商品数量',
    total_price      DECIMAL(10, 2)   DEFAULT 0 COMMENT '商品销售总价',
    logistics_no     VARCHAR(50)      DEFAULT '' COMMENT '物流单号',
    pay_channel      INT UNSIGNED     DEFAULT 0 COMMENT '支付渠道:1=支付宝,2=微信',
    pay_method       INT UNSIGNED     DEFAULT 0 COMMENT '支付方式:1=在线支付,2=货到付款',
    order_state      TINYINT UNSIGNED DEFAULT 0 COMMENT '订单状态: 0=待支付,1=已支付,待发货, 2=已发货/待收货,3=确认收货/已完成,4=用户关闭,5=平台关闭(商家),6=系统调度关闭',
    gmt_pay          DATETIME         DEFAULT NULL COMMENT '支付时间',
    gmt_create       DATETIME         DEFAULT NULL COMMENT '数据创建时间',
    gmt_modified     DATETIME         DEFAULT NULL COMMENT '数据最后修改时间',
    PRIMARY KEY (id)
) COMMENT '商城-订单' CHARSET = utf8mb4;

创建Spring Boot工程

name: spring-boot-sharding-demo

package name: cn.tedu.springboot.sharding.demo

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

使用IDEA连接数据库,用于查看数据库

在这里插入图片描述

引入maven依赖**

 		<!--引入sharding-jdbc和Spring Boot整合的Jar包-->
		<dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

   		 <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.16</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

点击右上角m标志,下载依赖,下载完毕之后,查看依赖是否下载成功

在这里插入图片描述

编写代码

分片规则配置

分片规则配置是sharding-jdbc进行对分库分表操作的重要依据,配置内容包括:数据源、主键生成策略、分片策略等。在application.properties中配置

创建资源文件:application.properties

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

server.port=9090
spring.application.name =sharding-demo

spring.main.allow-bean-definition-overriding = true
mybatis.configuration.map-underscore-to-camel-case=true


#配置数据源,名字可以自行定义,需要与下方配置保持一致
spring.shardingsphere.datasource.names=m1
#配置druid的连接池
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
#配置连接驱动
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://192.168.8.100:3306/tmall_server-mall?useUnicode=true
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123qqq...A

#指定mall_order表的数据分布情况,配置数据节点
spring.shardingsphere.sharding.tables.mall_order.actual-data-nodes=m1.mall_order_$->{1..2}
#配置表的主键生成策略,使用雪花算法可以自动解决主键冲突的问题
spring.shardingsphere.sharding.tables.mall_order.key-generator.column=id
spring.shardingsphere.sharding.tables.mall_order.key-generator.type=SNOWFLAKE
#配置表的分片策略,分片策略包含了分片健和分片算法
spring.shardingsphere.sharding.tables.mall_order.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.mall_order.table-strategy.inline.algorithm-expression=mall_order_$->{id % 2 + 1}
#打开SQL输出日志
spring.shardingsphere.props.sql.show=true

通过MyBatis新增数据

创建持久层接口: 在mapper包下创建OrderMapper

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

实现插入功能

package cn.tedu.springboot.sharding.demo.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface OrderMapper {

//编写新增方法,通过insert注解,指定新增的SQL语句
//当调用insertOrder方法的时候,等于执行上方注解指定的SQL语句
//Param注解用于指定新增时的参数
    @Insert("insert into mall_order(buyer_id,buyer_username,order_no,receiver_name) " +
            "values(#{buyerId},#{buyerUsername},#{orderNo},#{receiverName})")
    int insertOrder(@Param("buyerId")Long buyerId,@Param("buyerUsername")String buyerUsername
            ,@Param("orderNo")String orderNo,@Param("receiverName")String receiverName);

}

编写测试类:

在这里插入图片描述

package cn.tedu.springboot.sharding.demo;

import cn.tedu.springboot.sharding.demo.mapper.OrderMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBootShardingDemoApplicationTests {
    //注入对象,OrderMapper
    @Autowired
    OrderMapper orderMapper;

    @Test
    void contextLoads() {
    //调用insertOrder方法
        orderMapper.insertOrder(2000L, "硕博11帅哥", "jal12u911223", "1帅气老李");

    }

}

运行测试:

在这里插入图片描述

运行结果如下:

在这里插入图片描述

查看结果验证

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

为了更好的演示分表的插入效果,优化测试类:

package cn.tedu.springboot.sharding.demo;

import cn.tedu.springboot.sharding.demo.mapper.OrderMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBootShardingDemoApplicationTests {
    //注入对象,OrderMapper
    @Autowired
    OrderMapper orderMapper;

    @Test
    void contextLoads() {
    //使用循环调用insertOrder方法,连续插入10组数据
        for (int i = 0; i < 10; i++) {
            orderMapper.insertOrder(2000L, "硕博11帅哥", "jal12u911223", "1帅气老李");
        }

    }

}

运行结果如下:

id字段值为偶数的行对2求模取余+1结果为1的数据插入mall_order_1表

在这里插入图片描述

id字段值为奇数的行对2求模取余+1结果为2的数据插入mall_order_2表

在这里插入图片描述

实现查询功能

编写OrderMapper的查询方法

package cn.tedu.springboot.sharding.demo.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;
import java.util.Map;

@Mapper
public interface OrderMapper {

    //编写新增方法,通过insert注解,指定新增的SQL语句
	//当调用insertOrder方法的时候,等于执行上方注解指定的SQL语句
	//Param注解用于指定新增时的参数
    @Insert("insert into mall_order(buyer_id,buyer_username,order_no,receiver_name) " +
            "values(#{buyerId},#{buyerUsername},#{orderNo},#{receiverName})")
    int insertOrder(@Param("buyerId") Long buyerId, @Param("buyerUsername") String buyerUsername
            , @Param("orderNo") String orderNo, @Param("receiverName") String receiverName);

    //查询订单
    @Select("<script>" +
            "select" +
            " id,buyer_id,buyer_username" +
            " from mall_order " +
            " where id in " +
            "<foreach collection='orderIds' open='(' separator=',' close=')' item='id'>" +
            "#{id} " +
            "</foreach>" +
            "</script>")
    List<Map> selectOrders(@Param("orderIds") List<Long> orderIds);
}

编写测试方法

package cn.tedu.springboot.sharding.demo;

import cn.tedu.springboot.sharding.demo.mapper.OrderMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@SpringBootTest
class SpringBootShardingDemoApplicationTests {
    //注入对象,OrderMapper
    @Autowired
    OrderMapper orderMapper;

    @Test
    void contextLoads() {
        //调用insertOrder方法
        for (int i = 0; i < 10; i++) {
            orderMapper.insertOrder(2000L, "硕博11帅哥", "jal12u911223", "1帅气老李");
        }
    }
    //查询数据
    @Test
    public void selectOrderIds(){
        //此处的ID号可以换成自己的表记录中的数据
        List<Long> orderIds = Arrays.asList(1008183953959419904l,1008183953980391425l);
        List<Map> orders = orderMapper.selectOrders(orderIds);
        orders.forEach( order -> System.out.println(order));
    }

}


测试结果如下:

在这里插入图片描述

  • 能够看到来自mall_order_1表和mall_order_2表的数据

在这里插入图片描述

  • 分析查询过程
    • 在查询的过程中是对mall_order这个逻辑表进行查询
    • ShardingJDBC会自己对原SQL进行改写,实际上数据来自于mall_order_1表和mall_order_2表
    • ShardingJDBC还会自己对查询结果进行整合

在这里插入图片描述

流程分析

通过日志分析,Sharding-JDBC在拿到用户要执行的sql的后续工作

(1)解析SQL,获取片键值,在本例中是id

(2)Sharding-JDBC通过规则配置mall_order_$->{id % 2 + 1},知道了当id为偶数时,应该往mall_order_1表插数据,为奇数时,往mall_order_2插数据。

(3)Sharding-JDBC根据id的值改写SQL语句,改写后的SQL语句是真实所要执行的SQL语句。

(4)执行改写后的真实SQL语句

(5)将所有真正执行sql的结果进行汇总合并,返回。

其他配置方式

Sharding-JDBC官方提供了四种配置方式,除了properties以外,还有三种方式:

Java配置

     DataSource getShardingDataSource() throws SQLException {
         ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
         shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());
         shardingRuleConfig.getTableRuleConfigs().add(getOrderItemTableRuleConfiguration());
         shardingRuleConfig.getBindingTableGroups().add("t_order, t_order_item");
         shardingRuleConfig.getBroadcastTables().add("t_config");
         shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id", "ds${user_id % 2}"));
         shardingRuleConfig.setDefaultTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("order_id", new ModuloShardingTableAlgorithm()));
         return ShardingDataSourceFactory.createDataSource(createDataSourceMap(), shardingRuleConfig, new Properties());
     }
     
     private static KeyGeneratorConfiguration getKeyGeneratorConfiguration() {
         KeyGeneratorConfiguration result = new KeyGeneratorConfiguration("SNOWFLAKE", "order_id");
         return result;
     }
     
     TableRuleConfiguration getOrderTableRuleConfiguration() {
         TableRuleConfiguration result = new TableRuleConfiguration("t_order", "ds${0..1}.t_order${0..1}");
         result.setKeyGeneratorConfig(getKeyGeneratorConfiguration());
         return result;
     }
     
     TableRuleConfiguration getOrderItemTableRuleConfiguration() {
         TableRuleConfiguration result = new TableRuleConfiguration("t_order_item", "ds${0..1}.t_order_item${0..1}");
         return result;
     }
     
     Map<String, DataSource> createDataSourceMap() {
         Map<String, DataSource> result = new HashMap<>();
         result.put("ds0", DataSourceUtil.createDataSource("ds0"));
         result.put("ds1", DataSourceUtil.createDataSource("ds1"));
         return result;
     }

Yaml配置

dataSources:
  ds0: !!org.apache.commons.dbcp.BasicDataSource
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ds0
    username: root
    password: 
  ds1: !!org.apache.commons.dbcp.BasicDataSource
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ds1
    username: root
    password: 

shardingRule:  
  tables:
    t_order: 
      actualDataNodes: ds${0..1}.t_order${0..1}
      databaseStrategy:
        inline:
          shardingColumn: user_id
          algorithmExpression: ds${user_id % 2}
      tableStrategy: 
        inline:
          shardingColumn: order_id
          algorithmExpression: t_order${order_id % 2}
      keyGenerator:
        type: SNOWFLAKE
        column: order_id
    t_order_item:
      actualDataNodes: ds${0..1}.t_order_item${0..1}
      databaseStrategy:
        inline:
          shardingColumn: user_id
          algorithmExpression: ds${user_id % 2}
      tableStrategy:
        inline:
          shardingColumn: order_id
          algorithmExpression: t_order_item${order_id % 2}  
  bindingTables:
    - t_order,t_order_item
  broadcastTables:
    - t_config
  
  defaultDataSourceName: ds0
  defaultTableStrategy:
    none:
  defaultKeyGenerator:
    type: SNOWFLAKE
    column: order_id
  
props:
  sql.show: true

Spring命名空间配置

<?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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:sharding="http://shardingsphere.apache.org/schema/shardingsphere/sharding"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://shardingsphere.apache.org/schema/shardingsphere/sharding
                        http://shardingsphere.apache.org/schema/shardingsphere/sharding/sharding.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/tx
                        http://www.springframework.org/schema/tx/spring-tx.xsd">
    <context:annotation-config />

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="shardingDataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" p:database="MYSQL" />
        </property>
        <property name="packagesToScan" value="org.apache.shardingsphere.example.core.jpa.entity" />
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.hbm2ddl.auto">create</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" p:entityManagerFactory-ref="entityManagerFactory" />
    <tx:annotation-driven />

    <bean id="ds0" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/ds0" />
        <property name="username" value="root" />
        <property name="password" value="" />
    </bean>

    <bean id="ds1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/ds1" />
        <property name="username" value="root" />
        <property name="password" value="" />
    </bean>

    <bean id="preciseModuloDatabaseShardingAlgorithm" class="org.apache.shardingsphere.example.algorithm.PreciseModuloShardingDatabaseAlgorithm" />
    <bean id="preciseModuloTableShardingAlgorithm" class="org.apache.shardingsphere.example.algorithm.PreciseModuloShardingTableAlgorithm" />

    <sharding:standard-strategy id="databaseShardingStrategy" sharding-column="user_id" precise-algorithm-ref="preciseModuloDatabaseShardingAlgorithm" />
    <sharding:standard-strategy id="tableShardingStrategy" sharding-column="order_id" precise-algorithm-ref="preciseModuloTableShardingAlgorithm" />

    <sharding:key-generator id="orderKeyGenerator" type="SNOWFLAKE" column="order_id" />
    <sharding:key-generator id="itemKeyGenerator" type="SNOWFLAKE" column="order_item_id" />

    <sharding:data-source id="shardingDataSource">
        <sharding:sharding-rule data-source-names="ds0,ds1">
            <sharding:table-rules>
                <sharding:table-rule logic-table="t_order" actual-data-nodes="ds$->{0..1}.t_order$->{0..1}" database-strategy-ref="databaseShardingStrategy" table-strategy-ref="tableShardingStrategy" key-generator-ref="orderKeyGenerator" />
                <sharding:table-rule logic-table="t_order_item" actual-data-nodes="ds$->{0..1}.t_order_item$->{0..1}" database-strategy-ref="databaseShardingStrategy" table-strategy-ref="tableShardingStrategy" key-generator-ref="itemKeyGenerator" />
            </sharding:table-rules>
            <sharding:binding-table-rules>
                <sharding:binding-table-rule logic-tables="t_order, t_order_item" />
            </sharding:binding-table-rules>
            <sharding:broadcast-table-rules>
                <sharding:broadcast-table-rule table="t_config" />
            </sharding:broadcast-table-rules>
        </sharding:sharding-rule>
    </sharding:data-source>
</beans>

概念名词解释

在了解Sharding-JDBC的执行原理前,需要了解以下概念:

逻辑表: 水平拆分的数据表的总称。例:订单数据表根据主键尾数拆分为9张表,分别是mall_order_1mall_order_2mall_order_9,他们的逻辑表名为mall_order

**真实表: ** 在分片的数据库中真实存在的物理表。即上个示例中的 mall_order_1mall_order_9

数据节点: 数据分片的最小物理单元。由数据源名称和数据表组成,例:tmall-server-mall_0.mall_order_1

分片键: 用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。SQL中如果无分片字段,将执行全路由,性能较差。除了对单分片字段的支持,Sharding-Jdbc也支持根据多个字段进行分片。

执行原理

当Sharding-JDBC接受到一条SQL语句时,会陆续执行 SQL解析 => 查询优化 => SQL路由 => SQL改写 => SQL执行 => 结果归并,最终返回执行结果。

在这里插入图片描述

水平分库

水平分库是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。

在这里插入图片描述

(1)将原有tmall_server-mall库拆分为tmall_server-mall_1tmall_server-mall_2

-- --------------------------------------------------------
-- 主机:                           127.0.0.1
-- 服务器版本:                        10.3.7-MariaDB - mariadb.org binary distribution
-- 服务器操作系统:                      Win64
-- HeidiSQL 版本:                  9.4.0.5125
-- --------------------------------------------------------

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;


-- 导出 tmall-server-mall 的数据库结构
DROP DATABASE IF EXISTS `tmall-server-mall_1`;
CREATE DATABASE IF NOT EXISTS `tmall-server-mall_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `tmall-server-mall_1`;

-- 导出  表 tmall-server-mall.mall_order_1 结构
DROP TABLE IF EXISTS `mall_order_1`;
CREATE TABLE IF NOT EXISTS `mall_order_1` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '数据ID',
  `buyer_id` bigint(20) unsigned DEFAULT 0 COMMENT '用户ID',
  `buyer_username` varchar(50) DEFAULT '' COMMENT '用户名',
  `order_no` varchar(50) DEFAULT '' COMMENT '订单编号',
  `receiver_name` varchar(32) DEFAULT '' COMMENT '收货人',
  `receiver_phone` varchar(32) DEFAULT '' COMMENT '收货电话',
  `receiver_address` varchar(255) DEFAULT '' COMMENT '收货地址',
  `goods_num` int(10) unsigned DEFAULT 0 COMMENT '商品数量',
  `total_price` decimal(10,2) DEFAULT 0.00 COMMENT '商品销售总价',
  `logistics_no` varchar(50) DEFAULT '' COMMENT '物流单号',
  `pay_channel` int(10) unsigned DEFAULT 0 COMMENT '支付渠道:1=支付宝,2=微信',
  `pay_method` int(10) unsigned DEFAULT 0 COMMENT '支付方式:1=在线支付,2=货到付款',
  `order_state` tinyint(3) unsigned DEFAULT 0 COMMENT '订单状态: 0=待支付,1=已支付,待发货, 2=已发货/待收货,3=确认收货/已完成,4=用户关闭,5=平台关闭(商家),6=系统调度关闭',
  `gmt_pay` datetime DEFAULT NULL COMMENT '支付时间',
  `gmt_create` datetime DEFAULT NULL COMMENT '数据创建时间',
  `gmt_modified` datetime DEFAULT NULL COMMENT '数据最后修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1002699311877718017 DEFAULT CHARSET=utf8mb4 COMMENT='商城-订单';

-- 正在导出表  tmall-server-mall.mall_order_1 的数据:~5 rows (大约)
/*!40000 ALTER TABLE `mall_order_1` DISABLE KEYS */;
INSERT INTO `mall_order_1` (`id`, `buyer_id`, `buyer_username`, `order_no`, `receiver_name`, `receiver_phone`, `receiver_address`, `goods_num`, `total_price`, `logistics_no`, `pay_channel`, `pay_method`, `order_state`, `gmt_pay`, `gmt_create`, `gmt_modified`) VALUES
	(1002699311798026240, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311814803456, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311839969280, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311856746496, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311877718016, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL);
/*!40000 ALTER TABLE `mall_order_1` ENABLE KEYS */;

-- 导出  表 tmall-server-mall.mall_order_2 结构
DROP TABLE IF EXISTS `mall_order_2`;
CREATE TABLE IF NOT EXISTS `mall_order_2` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '数据ID',
  `buyer_id` bigint(20) unsigned DEFAULT 0 COMMENT '用户ID',
  `buyer_username` varchar(50) DEFAULT '' COMMENT '用户名',
  `order_no` varchar(50) DEFAULT '' COMMENT '订单编号',
  `receiver_name` varchar(32) DEFAULT '' COMMENT '收货人',
  `receiver_phone` varchar(32) DEFAULT '' COMMENT '收货电话',
  `receiver_address` varchar(255) DEFAULT '' COMMENT '收货地址',
  `goods_num` int(10) unsigned DEFAULT 0 COMMENT '商品数量',
  `total_price` decimal(10,2) DEFAULT 0.00 COMMENT '商品销售总价',
  `logistics_no` varchar(50) DEFAULT '' COMMENT '物流单号',
  `pay_channel` int(10) unsigned DEFAULT 0 COMMENT '支付渠道:1=支付宝,2=微信',
  `pay_method` int(10) unsigned DEFAULT 0 COMMENT '支付方式:1=在线支付,2=货到付款',
  `order_state` tinyint(3) unsigned DEFAULT 0 COMMENT '订单状态: 0=待支付,1=已支付,待发货, 2=已发货/待收货,3=确认收货/已完成,4=用户关闭,5=平台关闭(商家),6=系统调度关闭',
  `gmt_pay` datetime DEFAULT NULL COMMENT '支付时间',
  `gmt_create` datetime DEFAULT NULL COMMENT '数据创建时间',
  `gmt_modified` datetime DEFAULT NULL COMMENT '数据最后修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1002699311865135106 DEFAULT CHARSET=utf8mb4 COMMENT='商城-订单';

-- 正在导出表  tmall-server-mall.mall_order_2 的数据:~11 rows (大约)
/*!40000 ALTER TABLE `mall_order_2` DISABLE KEYS */;
INSERT INTO `mall_order_2` (`id`, `buyer_id`, `buyer_username`, `order_no`, `receiver_name`, `receiver_phone`, `receiver_address`, `goods_num`, `total_price`, `logistics_no`, `pay_channel`, `pay_method`, `order_state`, `gmt_pay`, `gmt_create`, `gmt_modified`) VALUES
	(1002698066274287617, 1000, '硕博帅哥', 'jal12u91123', '帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002698385767006209, 1000, '硕博帅哥', 'jal12u91123', '帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002698438611042305, 1000, '硕博帅哥', 'jal12u91123', '帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002698520777457665, 1000, '硕博帅哥', 'jal12u91123', '帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002698562431090689, 1000, '硕博帅哥', 'jal12u91123', '帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002698715539963905, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311512813569, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311806414849, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311827386369, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311848357889, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311865135105, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL);
/*!40000 ALTER TABLE `mall_order_2` ENABLE KEYS */;

/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;

-- --------------------------------------------------------
-- 主机:                           127.0.0.1
-- 服务器版本:                        10.3.7-MariaDB - mariadb.org binary distribution
-- 服务器操作系统:                      Win64
-- HeidiSQL 版本:                  9.4.0.5125
-- --------------------------------------------------------

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;


-- 导出 tmall-server-mall 的数据库结构
DROP DATABASE IF EXISTS `tmall-server-mall_2`;
CREATE DATABASE IF NOT EXISTS `tmall-server-mall_2` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `tmall-server-mall_2`;

-- 导出  表 tmall-server-mall.mall_order_1 结构
DROP TABLE IF EXISTS `mall_order_1`;
CREATE TABLE IF NOT EXISTS `mall_order_1` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '数据ID',
  `buyer_id` bigint(20) unsigned DEFAULT 0 COMMENT '用户ID',
  `buyer_username` varchar(50) DEFAULT '' COMMENT '用户名',
  `order_no` varchar(50) DEFAULT '' COMMENT '订单编号',
  `receiver_name` varchar(32) DEFAULT '' COMMENT '收货人',
  `receiver_phone` varchar(32) DEFAULT '' COMMENT '收货电话',
  `receiver_address` varchar(255) DEFAULT '' COMMENT '收货地址',
  `goods_num` int(10) unsigned DEFAULT 0 COMMENT '商品数量',
  `total_price` decimal(10,2) DEFAULT 0.00 COMMENT '商品销售总价',
  `logistics_no` varchar(50) DEFAULT '' COMMENT '物流单号',
  `pay_channel` int(10) unsigned DEFAULT 0 COMMENT '支付渠道:1=支付宝,2=微信',
  `pay_method` int(10) unsigned DEFAULT 0 COMMENT '支付方式:1=在线支付,2=货到付款',
  `order_state` tinyint(3) unsigned DEFAULT 0 COMMENT '订单状态: 0=待支付,1=已支付,待发货, 2=已发货/待收货,3=确认收货/已完成,4=用户关闭,5=平台关闭(商家),6=系统调度关闭',
  `gmt_pay` datetime DEFAULT NULL COMMENT '支付时间',
  `gmt_create` datetime DEFAULT NULL COMMENT '数据创建时间',
  `gmt_modified` datetime DEFAULT NULL COMMENT '数据最后修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1002699311877718017 DEFAULT CHARSET=utf8mb4 COMMENT='商城-订单';

-- 正在导出表  tmall-server-mall.mall_order_1 的数据:~5 rows (大约)
/*!40000 ALTER TABLE `mall_order_1` DISABLE KEYS */;
INSERT INTO `mall_order_1` (`id`, `buyer_id`, `buyer_username`, `order_no`, `receiver_name`, `receiver_phone`, `receiver_address`, `goods_num`, `total_price`, `logistics_no`, `pay_channel`, `pay_method`, `order_state`, `gmt_pay`, `gmt_create`, `gmt_modified`) VALUES
	(1002699311798026240, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311814803456, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311839969280, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311856746496, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311877718016, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL);
/*!40000 ALTER TABLE `mall_order_1` ENABLE KEYS */;

-- 导出  表 tmall-server-mall.mall_order_2 结构
DROP TABLE IF EXISTS `mall_order_2`;
CREATE TABLE IF NOT EXISTS `mall_order_2` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '数据ID',
  `buyer_id` bigint(20) unsigned DEFAULT 0 COMMENT '用户ID',
  `buyer_username` varchar(50) DEFAULT '' COMMENT '用户名',
  `order_no` varchar(50) DEFAULT '' COMMENT '订单编号',
  `receiver_name` varchar(32) DEFAULT '' COMMENT '收货人',
  `receiver_phone` varchar(32) DEFAULT '' COMMENT '收货电话',
  `receiver_address` varchar(255) DEFAULT '' COMMENT '收货地址',
  `goods_num` int(10) unsigned DEFAULT 0 COMMENT '商品数量',
  `total_price` decimal(10,2) DEFAULT 0.00 COMMENT '商品销售总价',
  `logistics_no` varchar(50) DEFAULT '' COMMENT '物流单号',
  `pay_channel` int(10) unsigned DEFAULT 0 COMMENT '支付渠道:1=支付宝,2=微信',
  `pay_method` int(10) unsigned DEFAULT 0 COMMENT '支付方式:1=在线支付,2=货到付款',
  `order_state` tinyint(3) unsigned DEFAULT 0 COMMENT '订单状态: 0=待支付,1=已支付,待发货, 2=已发货/待收货,3=确认收货/已完成,4=用户关闭,5=平台关闭(商家),6=系统调度关闭',
  `gmt_pay` datetime DEFAULT NULL COMMENT '支付时间',
  `gmt_create` datetime DEFAULT NULL COMMENT '数据创建时间',
  `gmt_modified` datetime DEFAULT NULL COMMENT '数据最后修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1002699311865135106 DEFAULT CHARSET=utf8mb4 COMMENT='商城-订单';

-- 正在导出表  tmall-server-mall.mall_order_2 的数据:~11 rows (大约)
/*!40000 ALTER TABLE `mall_order_2` DISABLE KEYS */;
INSERT INTO `mall_order_2` (`id`, `buyer_id`, `buyer_username`, `order_no`, `receiver_name`, `receiver_phone`, `receiver_address`, `goods_num`, `total_price`, `logistics_no`, `pay_channel`, `pay_method`, `order_state`, `gmt_pay`, `gmt_create`, `gmt_modified`) VALUES
	(1002698066274287617, 1000, '硕博帅哥', 'jal12u91123', '帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002698385767006209, 1000, '硕博帅哥', 'jal12u91123', '帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002698438611042305, 1000, '硕博帅哥', 'jal12u91123', '帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002698520777457665, 1000, '硕博帅哥', 'jal12u91123', '帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002698562431090689, 1000, '硕博帅哥', 'jal12u91123', '帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002698715539963905, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311512813569, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311806414849, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311827386369, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311848357889, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL),
	(1002699311865135105, 2000, '硕博11帅哥', 'jal12u911223', '1帅气老李', '', '', 0, 0.00, '', 0, 0, 0, NULL, NULL, NULL);
/*!40000 ALTER TABLE `mall_order_2` ENABLE KEYS */;

/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;

(2)分片规则修改

由于数据库拆分了两个,这里需要配置两个数据源(m1和m2,注意也要把原来的库改为两个新的库)

分库需要配置分库的策略,和分表策略的意义类似,通过分库策略实现数据操作针对分库的数据库进行操作。

server.port=9090
spring.application.name =sharding-demo

spring.main.allow-bean-definition-overriding = true
mybatis.configuration.map-underscore-to-camel-case=true


#配置数据源,名字可以自行定义,需要与下方配置保持一致
spring.shardingsphere.datasource.names=m1,m2

#m1数据源配置
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://192.168.8.100:3306/tmall-server-mall_1?useUnicode=true
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123qqq...A
#m2数据源配置
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://192.168.8.100:3306/tmall-server-mall_2?useUnicode=true
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=123qqq...A

#配置分库策略,以buyer_id为分片键,分片策略为buyer_id %2+1
#buyer_id 为偶数操作m1数据源,buyer_id为奇数则操作m2数据源
spring.shardingsphere.sharding.tables.mall_order.database-strategy.inline.sharding-column=buyer_id
spring.shardingsphere.sharding.tables.mall_order.database-strategy.inline.algorithm-expression=m$->{buyer_id % 2 + 1}

#指定mall_order表的数据分布情况,配置数据节点
spring.shardingsphere.sharding.tables.mall_order.actual-data-nodes=m1.mall_order_$->{1..2}
#指定主键生成策略为SNOWFLAKE,避免主键重复
spring.shardingsphere.sharding.tables.mall_order.key-generator.column=id
spring.shardingsphere.sharding.tables.mall_order.key-generator.type=SNOWFLAKE
#配置表的分片策略,分片策略包含了分片健和分片算法
spring.shardingsphere.sharding.tables.mall_order.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.mall_order.table-strategy.inline.algorithm-expression=mall_order_$->{id % 2 + 1}
#打开SQL输出日志
spring.shardingsphere.props.sql.show=true

为了方便验证,删除原有数据

mysql> DELETE FROM `tmall-server-mall_1`.mall_order_1;
mysql> DELETE FROM `tmall-server-mall_1`.mall_order_2;
mysql> DELETE FROM `tmall-server-mall_2`.mall_order_1;
mysql> DELETE FROM `tmall-server-mall_2`.mall_order_2;

编写测试类验证,因为水平分库是根据buyer_id进行的分片,所以每次循环都应该不一样,所以为buyer_id+i

package cn.tedu.springboot.sharding.demo;

import cn.tedu.springboot.sharding.demo.mapper.OrderMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@SpringBootTest
class SpringBootShardingDemoApplicationTests {
    //注入对象,OrderMapper
    @Autowired
    OrderMapper orderMapper;

    @Test
    void contextLoads() {
        //调用insertOrder方法
        for (int i = 0; i < 10; i++) {
            //此处让buyer_id+i,分库的分片规则是按照buyer_id进行的分片,所以需要每次都不一样
            orderMapper.insertOrder(2000L+i, "硕博11帅哥", "jal12u911223", "1帅气老李");
        }
    }
    //查询数据
    @Test
    public void selectOrderIds(){
        //此处的ID号可以换成自己的表记录中的数据
        List<Long> orderIds = Arrays.asList(1008183953959419904l,1008183953980391425l);
        List<Map> orders = orderMapper.selectOrders(orderIds);
        orders.forEach( order -> System.out.println(order));
    }

}

在这里插入图片描述

通过执行发现,

根据库的分片规则(buyer_id)有的数据写进入了tmall-server-mall_1库,有的数据进入了tmall-server-mall_2库

根据表的分片规则(id)有的数据进入了tmall-server-mall_1.mall_order_1表

根据表的分片规则(id)有的数据进入了tmall-server-mall_1.mall_order_2表

根据表的分片规则(id)有的数据进入了tmall-server-mall_2.mall_order_1表

根据表的分片规则(id)有的数据进入了tmall-server-mall_2.mall_order_2表

buyer_id%2+1决定进入哪个库

id%2+1决定进入库的哪个表

在这里插入图片描述

验证查询操作,修改配置文件application.properties

server.port=9090
spring.application.name =sharding-demo

spring.main.allow-bean-definition-overriding = true
mybatis.configuration.map-underscore-to-camel-case=true


#配置数据源,名字可以自行定义,需要与下方配置保持一致
spring.shardingsphere.datasource.names=m1,m2

#m1数据源配置
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://192.168.8.100:3306/tmall-server-mall_1?useUnicode=true
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123qqq...A
#m2数据源配置
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://192.168.8.100:3306/tmall-server-mall_2?useUnicode=true
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=123qqq...A

#配置分库策略,以buyer_id为分片键,分片策略为buyer_id %2+1
#buyer_id 为偶数操作m1数据源,buyer_id为奇数则操作m2数据源
spring.shardingsphere.sharding.tables.mall_order.database-strategy.inline.sharding-column=buyer_id
spring.shardingsphere.sharding.tables.mall_order.database-strategy.inline.algorithm-expression=m$->{buyer_id % 2 + 1}

#指定mall_order表的数据分布情况,配置数据节点,有两个库,修改这里,告知有两个库
spring.shardingsphere.sharding.tables.mall_order.actual-data-nodes=m$->{1..2}.mall_order_$->{1..2}
#指定主键生成策略为SNOWFLAKE,避免主键重复
spring.shardingsphere.sharding.tables.mall_order.key-generator.column=id
spring.shardingsphere.sharding.tables.mall_order.key-generator.type=SNOWFLAKE
#配置表的分片策略,分片策略包含了分片健和分片算法
spring.shardingsphere.sharding.tables.mall_order.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.mall_order.table-strategy.inline.algorithm-expression=mall_order_$->{id % 2 + 1}
#打开SQL输出日志
spring.shardingsphere.props.sql.show=true

修改测试类中查询部分的id值

package cn.tedu.springboot.sharding.demo;

import cn.tedu.springboot.sharding.demo.mapper.OrderMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@SpringBootTest
class SpringBootShardingDemoApplicationTests {
    //注入对象,OrderMapper
    @Autowired
    OrderMapper orderMapper;

    @Test
    void contextLoads() {
        //调用insertOrder方法
        for (int i = 0; i < 10; i++) {
            orderMapper.insertOrder(2000L+i, "硕博11帅哥", "jal12u911223", "1帅气老李");
        }
    }
    //查询数据
    @Test
    public void selectOrderIds(){
        //此处的ID号可以换成自己的表记录中的数据,数据从表中查询即可
        List<Long> orderIds = Arrays.asList(1008204780222283776L);
        List<Map> orders = orderMapper.selectOrders(orderIds);
        orders.forEach( order -> System.out.println(order));
    }

}

在这里插入图片描述

已经执行了查询,但此刻在m1库和m2库都做了查询,是因为分库的时候是根据buyer_id进行的分片

在这里插入图片描述

解决方案,查询的时候只在一个库里边查询

修改OrderMapper,支持根据buyerid进行查找

package cn.tedu.springboot.sharding.demo.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;
import java.util.Map;

@Mapper
public interface OrderMapper {

    //编写新增方法,通过insert注解,指定新增的SQL语句
    //当调用insertOrder方法的时候,等于执行上方注解指定的SQL语句
    //Param注解用于指定新增时的参数
    @Insert("insert into mall_order(buyer_id,buyer_username,order_no,receiver_name) " +
            "values(#{buyerId},#{buyerUsername},#{orderNo},#{receiverName})")
    int insertOrder(@Param("buyerId") Long buyerId, @Param("buyerUsername") String buyerUsername
            , @Param("orderNo") String orderNo, @Param("receiverName") String receiverName);

    //查询订单
    @Select("<script>" +
            "select" +
            " id,buyer_id,buyer_username" +
            " from mall_order " +
            " where id in " +
            "<foreach collection='orderIds' open='(' separator=',' close=')' item='id'>" +
            "#{id} " +
            "</foreach>" +
            "and buyer_id = #{buyerID }" +
            "</script>")
    List<Map> selectOrders(@Param("orderIds") List<Long> orderIds,@Param("buyerID") Long buyerID );
}

修改测试类

package cn.tedu.springboot.sharding.demo;

import cn.tedu.springboot.sharding.demo.mapper.OrderMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@SpringBootTest
class SpringBootShardingDemoApplicationTests {
    //注入对象,OrderMapper
    @Autowired
    OrderMapper orderMapper;

    @Test
    void contextLoads() {
        //调用insertOrder方法
        for (int i = 0; i < 10; i++) {
            orderMapper.insertOrder(2000L+i, "硕博11帅哥", "jal12u911223", "1帅气老李");
        }
    }
    //查询数据
    @Test
    public void selectOrderIds(){
        //此处的ID号可以换成自己的表记录中的数据,数据从表中查询即可
        //增加buyerId的定义,通过orderIds和buyerId来查询
        //注意buyerId和id的值要对应
        List<Long> orderIds = Arrays.asList(1008204780222283776L);
        Long buyerId=2001L;
        List<Map> orders = orderMapper.selectOrders(orderIds,buyerId);
        orders.forEach( order -> System.out.println(order));
    }

}

在这里插入图片描述

执行流程分析

SQL 解析

  • insert into mall_order(buyer_id,buyer_username,order_no,receiver_name) values(1999L,"硕博11帅哥","jal12u911223","1帅气老李")
    
  • Sharding-JDBC 解析该SQL语句,识别出需要插入 mall_order 表,并提取出各个字段和值

路由计算

  • 根据分片配置,Sharding-JDBC 使用 buyerId=1999 进行路由计算,决定该记录应插入到 m2:tall-mall-order_2 库中
  • 再根据 id 值进行路由计算,决定该记录插入到 mall_order_1 表中

SQL重写:

  • 构建新的 SQL 语句

  • insert into mall_order_1 (buyer_id, buyer_username, order_no, receiver_name, id) VALUES (?, ?, ?, ?, ?) ::: [1999, 硕博11帅哥, jal12u911223, 1帅气老李, 1005082312909520896]
    

在这里插入图片描述

SQL执行:

  • 将重写后的 SQL 语句发送到 m2 数据库的表 mall_order_1 中进行插入操作。

  • 返回插入成功的结果,或处理任何可能出现的错误。

垂直分库(补充)

垂直分库是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用

创建tmall-server-user`数据库

DROP DATABASE IF EXISTS `tmall-server-user`;
CREATE DATABASE IF NOT EXISTS `tmall-server-user` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `tmall-server-user`;

在tmall-server-user库中创建 mall_user表

CREATE TABLE mall_user
(
    id             BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '数据ID',
    username       VARCHAR(32)      DEFAULT '' COMMENT '用户名',
    password       VARCHAR(128)     DEFAULT '' COMMENT '密码(密文)',
    avatar         VARCHAR(255)     DEFAULT '' COMMENT '头像URL',
    phone          VARCHAR(32)      DEFAULT '' COMMENT '手机号码',
    email          VARCHAR(64)      DEFAULT '' COMMENT '电子邮箱',
    description    VARCHAR(255)     DEFAULT '' COMMENT '简介',
    enable         TINYINT UNSIGNED DEFAULT 0 COMMENT '是否启用,1=启用,0=未启用',
    last_login_ip  VARCHAR(32)      DEFAULT '' COMMENT '最后登录IP地址(冗余)',
    login_count    INT UNSIGNED     DEFAULT 0 COMMENT '累计登录次数(冗余)',
    gmt_last_login DATETIME         DEFAULT NULL COMMENT '最后登录时间(冗余)',
    gmt_create     DATETIME         DEFAULT NULL COMMENT '数据创建时间',
    gmt_modified   DATETIME         DEFAULT NULL COMMENT '数据最后修改时间',
    PRIMARY KEY (id)
) DEFAULT CHARSET = utf8mb4 COMMENT ='用户';

配置分片策略

server.port=9090
spring.application.name =sharding-demo

spring.main.allow-bean-definition-overriding = true
mybatis.configuration.map-underscore-to-camel-case=true


#配置数据源,名字可以自行定义,需要与下方配置保持一致
spring.shardingsphere.datasource.names=m1,m2,m3

#m1数据源配置
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/tmall-server-mall_1?useUnicode=true
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=root
#m2数据源配置
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://localhost:3306/tmall-server-mall_2?useUnicode=true
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=root

#m3数据源配置
spring.shardingsphere.datasource.m3.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m3.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m3.url=jdbc:mysql://localhost:3306/tmall-server-user?useUnicode=true
spring.shardingsphere.datasource.m3.username=root
spring.shardingsphere.datasource.m3.password=root

#配置分库策略,以user_id为分片键,分片策略为user_id %2+1 ,user_id 为偶数操作m1 数据源,为奇数则操作m2数据源
spring.shardingsphere.sharding.tables.mall_order.database-strategy.inline.sharding-column=buyer_id
spring.shardingsphere.sharding.tables.mall_order.database-strategy.inline.algorithm-expression=m$->{buyer_id % 2 + 1}

#指定mall_order表的数据分布情况,配置数据节点
#spring.shardingsphere.sharding.tables.mall_order.actual-data-nodes=m1.mall_order_$->{1..2}
spring.shardingsphere.sharding.tables.mall_order.actual-data-nodes=m$->{1..2}.mall_order_$->{1..2}
#配置tmall-order-user下的数据节点
spring.shardingsphere.sharding.tables.mall_user.actual-data-nodes=m3.mall_user

#指定主键生成策略为SNOWFLAKE,避免主键重复
spring.shardingsphere.sharding.tables.mall_order.key-generator.column=id
spring.shardingsphere.sharding.tables.mall_order.key-generator.type=SNOWFLAKE

#配置表的分片策略,分片策略包含了分片健和分片算法
spring.shardingsphere.sharding.tables.mall_order.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.mall_order.table-strategy.inline.algorithm-expression=mall_order_$->{id % 2 + 1}

#配置表的分片策略,分片策略包含了分片健和分片算法
spring.shardingsphere.sharding.tables.mall_user.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.mall_user.table-strategy.inline.algorithm-expression=mall_user


#打开SQL输出日志
spring.shardingsphere.props.sql.show=true

用户数据操作

编写UserMapper持久层接口,完成查询方法和新增方法定义

package cn.tedu.springboot.sharding.demo.dao;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;
import java.util.Map;

@Mapper
public interface UserMapper {

    @Insert("insert into mall_user(username,password) values(#{username},#{password})")
    int insertUser(@Param("username") String username,@Param("password")String password);

    //查询用户
    @Select("<script>" +
            "select" +
            " *" +
            " from mall_user " +
            " where id in " +
            "<foreach collection='userIds' open='(' separator=',' close=')' item='id'>" +
            "#{id} " +
            "</foreach>" +
            "</script>")
    List<Map> selectOrders(@Param("userIds") List<Long> userIds);

}

在SpringBootShardingDemoApplicationTests类中,进行userMapper的数据新增功能测试

@Autowired
    private UserMapper userMapper;

    @Test
    public void insertUser() {
        for (int i = 0; i < 10; i++) {
            userMapper.insertUser("liyuan","123456");
        }
    }

    @Test
    public void selectUserByIds() {
        List<Map> maps =
                userMapper.selectUsers(Arrays.asList(1L,2L,3L));

        maps.forEach(map -> System.out.println(map));
    }

测试结果如下:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2257682.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA 16 ,Verilog中的位宽:深入理解与应用

目录 前言 一. 位宽的基本概念 二. 位宽的定义方法 1. 使用向量变量定义位宽 ① 向量类型及位宽指定 ② 位宽范围及位索引含义 ③ 存储数据与字节数据 2. 使用常量参数定义位宽 3. 使用宏定义位宽 4. 使用[:][-:]操作符定义位宽 1. 详细解释 : 操作符 -: 操作符 …

HTML:表格重点

用表格就用table caption为该表上部信息&#xff0c;用来说明表的作用 thead为表头主要信息&#xff0c;效果加粗 tbody为表格中的主体内容 tr是 table row 表格的行 td是table data th是table heading表格标题 &#xff0c;一般表格第一行的数据都是table heading

hbuilder 本地插件配置

插件存放路径&#xff0c;项目根目录nativeplugins下&#xff0c;没有就新建。 aar文件存放路径\nativeplugins\pda-module\android package.json存放路径\nativeplugins\module\ 配置package.json文件 { "name": "本地插件", "id": &quo…

大模型应用的数字能源数据集

除了尚须时日的量子计算解决算力效率和能源问题&#xff0c;以及正在路上的超越transformer的全新模型架构外&#xff0c;无疑是“数据集”&#xff0c;准确讲是“高质量大规模多样性的数据集”。数据集是大模型发展的核心要素之一&#xff0c;是大计算的标的物&#xff0c;是实…

飞书解除复制,下载文件限制终极方案

1.通过移除copy 事件&#xff0c;可以复制文档内容&#xff0c;但是飞书表格增加了键盘按键事件&#xff0c;表格无法复制&#xff0c;下载 2.通过chrome插件&#xff0c;可以复制clould document converter 可以实现下载飞书文档&#xff0c;但是无法下载表格 而且无法识别自定…

Java面试题精选:设计模式(二)

1、装饰器模式与代理模式的区别 1&#xff09;代理模式(Proxy Design Pattern ) 原始定义是&#xff1a;让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问&#xff0c;并允许将请求提交给对象前后进行一些处理。 代理模式的适用场景 功能增强 当需要对一个对…

自然语言处理:从入门到精通全指引

一、引言 自然语言处理&#xff08;NLP&#xff09;作为人工智能领域的关键分支&#xff0c;旨在让计算机理解、生成和处理人类语言&#xff0c;近年来取得了令人瞩目的成就&#xff0c;在智能客服、机器翻译、文本分析、语音助手等众多领域发挥着重要作用。从入门到精通自然语…

Typora 修改默认的高亮颜色

shift F12 参考 怎么给typora添加颜色&#xff1f;

(1)Quartus中如何在外设FLASH中固化jic文件

&#xff08;1&#xff09;在产生jic文件前&#xff0c;必须已经综合通过&#xff0c;生成了sof文件 &#xff08;2&#xff09;点击file-convert Programming Files... &#xff08;3&#xff09;文件类型选择jic文件&#xff0c;flsh型号设定为EPCS128 &#xff08;4&#…

OpenAI2024-12D-3:Sora 发布,谁更胜一筹——Sora 与可灵的全面前瞻对比

藏了一年&#xff0c;终于OpenAI在12天活动的第三天&#xff0c;正式发布了其全新创意工具——Sora&#xff0c;这款工具凭借其强大的文本到视频生成能力和高度的创作自由度&#xff0c;迅速吸引了广大创作者的目光。与此同时&#xff0c;已经在视频创作领域有着成熟表现的可灵…

重生之我在异世界学智力题(4)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言渡河问题&#xff08;1&#xff09;问…

福州大学《2024年812自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《福州大学812自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2024年真题 Part1&#xff1a;2024年完整版真题 2024年真题

实现盘盈单自动化处理:吉客云与金蝶云星空数据对接

盘盈单103v2对接其他入库&#xff1a;吉客云数据集成到金蝶云星空 在企业信息化管理中&#xff0c;数据的高效流转和准确性至关重要。本文将分享一个实际案例&#xff0c;展示如何通过轻易云数据集成平台&#xff0c;将吉客云的数据无缝对接到金蝶云星空&#xff0c;实现盘盈单…

Meta Llama 3.3 70B:性能卓越且成本效益的新选择

Meta Llama 3.3 70B&#xff1a;性能卓越且成本效益的新选择 引言 在人工智能领域&#xff0c;大型语言模型一直是研究和应用的热点。Meta公司最近发布了其最新的Llama系列模型——Llama 3.3 70B&#xff0c;这是一个具有70亿参数的生成式AI模型&#xff0c;它在性能上与4050…

【优选算法 前缀和】前缀和算法模板详解:一维前缀 & 与二维前缀和

一维前缀和 题目解析 算法原理 解法一&#xff1a;暴力解法 简单模拟&#xff0c;读完题意有 q 次询问&#xff0c;给哪两个数&#xff0c;就求哪段区间的和并且返回&#xff0c;这样的做法&#xff0c;时间复杂度为O(N*q)&#xff0c;这个时间复杂度会超时&#xf…

yarn : 无法加载文件 C:\Users\L\AppData\Roaming\npm\yarn.ps1,因为在此系统上禁

关于执行安装yarn命令后执行yarn -v报错&#xff1a; 先确认执行安装yarn命令是否有误 # 安装yarn npm install yarn -g 终端输入set-ExecutionPolicy RemoteSigned 当然如果yarn -v仍然执行失败&#xff0c;考虑使用管理员方式运行IDEA&#xff0c; 注&#xff1a;如上操作…

设计模式——单例模式和工厂模式

单例模式:一个类只创建一个类对象&#xff08;节省内存减少类对象数量,一个类对象多次重复使用&#xff09; 格式: class a: pass ba() z1b z2b #z1和z2为同一个类对象 工厂模式&#xff1a;&#xff08;大批量创建具体的类对象,统一类对象入口便于维护&#xf…

UVM之寄存器模型生成

1.采用python脚本生成寄存器模型 首先用excel表格做好寄存器描述 然后编写脚本生成.ralf文件 &#xff08;1&#xff09;首先通过openpyxl读取EXCEL表格&#xff0c; workbook openpyxl.load_workbook(reg.xlsx) # 返回一个workbook数据类型的值 &#xff08;2&#xff…

web 期末作业简单设计网页——“我的家乡”网站简单设计

1、网页效果 首页 七彩云南页 旅游攻略页 用户页面 2、源代码 首页 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>首页</title><link rel"stylesheet" href"out.css&quo…

Nanolog起步笔记-10-log解压过程(4)寻找meta续2

Nanolog起步笔记-10-log解压过程4寻找meta续2 写在前面重新开始trace readDictionaryFragment读取meta头部读入每个记录createMicroCode读入头部&#xff0c;和文件名 切分format字符串PrintFragment 后记 写在前面 前面的工作&#xff0c;已做打下令人有信心的基础。 重新开…