springBoot使用ShardingJDBC实现分表

news2024/11/29 2:29:19

ShardingSphere的介绍

ShardingSphere是一款起源于当当网内部的应用框架。2015年在当当网内部诞 生,最初就叫ShardingJDBC。2016年的时候,由其中一个主要的开发人员张亮, 带入到京东数科,组件团队继续开发。在国内历经了当当网、电信翼支付、京东数 科等多家大型互联网企业的考验,在2017年开始开源。并逐渐由原本只关注于关系 型数据库增强工具的ShardingJDBC升级成为一整套以数据分片为基础的数据生态 圈,更名为ShardingSphere。到2020年4月,已经成为了Apache软件基金会的顶 级项目。 ShardingSphere包含三个重要的产品,ShardingJDBC、ShardingProxy和 ShardingSidecar。其中sidecar是针对service mesh定位的一个分库分表插件,目 前在规划中。而我们今天学习的重点是ShardingSphere的JDBC这个组 件。 其中,ShardingJDBC是用来做客户端分库分表的产品,而ShardingProxy是用 来做服务端分库分表的产品。这两者定位有什么区别呢?我们看下官方资料中给出 的两个重要的图:

 

 

 

 

1核心概念

分库分表

分库,显而易见,就是一个数据库分成多个数据库,部署到不同机器。

分表,就是一个数据库表分成多个表。

分片

一般在提到分库分表的时候,大多是以水平切分模式(水平分库、分表)为基础来说的,

数据分片将原本一张数据量较大的表例如 t_order 拆分生成数个表结构完全一致的小数据量表 t_order_0、t_order_1、···、t_order_n,每张表只存储原大表中的一部分数据,当执行一条SQL时会通过 分库策略分片策略 将数据分散到不同的数据库、表内。

87a6703b051db1fc739538e1d1230faa.png

数据节点

数据节点是分库分表中一个不可再分的最小数据单元(表),它由数据源名称和数据表组成,例如上图中 order_db_1.t_order_0、order_db_2.t_order_1 就表示一个数据节点。

逻辑表

逻辑表是指一组具有相同逻辑和数据结构表的总称。

比如将订单表 t_order 拆分成 t_order_0 ···  t_order_9 等 10张表。

此时会发现分库分表以后数据库中已不在有 t_order 这张表,取而代之的是 t_order_n,但在代码中写 SQL 依然按 t_order 来写。此时 t_order 就是这些拆分表的逻辑表。

真实表

真实表也就是上边提到的 t_order_n 数据库中真实存在的物理表。

分片键

用于分片的数据库字段。将 t_order 表分片以后,当执行一条SQL时,通过对字段 order_id 取模的方式来决定,这条数据该在哪个数据库中的哪个表中执行,此时 order_id 字段就是 t_order 表的分片健。

 

 实战

以下演示分表,将逻辑订单表 t_order_info 根据订单id字段拆分成3张真实表(t_order_info_0,t_order_info_1,t_order_info_2)

1.创建订单表

 创建好了同时复制另2张表,总共3张表。

2. 创建springboot微服务项目

新建一个微服务项目,引入sharding包

<dependency>
	<groupId>org.apache.shardingsphere</groupId>
	<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
	<version>5.1.2</version>
</dependency>

增加配置

# 配置信息存储方式:内存
spring.shardingsphere.mode.type=Memory

# 显示运行的sql
spring.shardingsphere.props.sql.show=true
# 数据源配置
spring.shardingsphere.datasource.names=ds0

# 数据源0ds0的配置
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://127.0.0.1:3306/order?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai&useAffectedRows=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456

# 表t_order_info
# 分表规则(由于只分表, 不分库, 所以分库直接采用默认的即可)
# 行内表达式分片算法
spring.shardingsphere.rules.sharding.sharding-algorithms.order-inline.type=INLINE
# id列的值%3,等于1就选t_order_info_0表,等于2就选t_order_info_1表,等于3就选t_order_info_2表
spring.shardingsphere.rules.sharding.sharding-algorithms.order-inline.props.algorithm-expression=t_order_info_$->{id % 3}
# 真实表分布,order库的t_order_info_0,t_order_info_1,t_order_info_2表
spring.shardingsphere.rules.sharding.tables.t_order_info.actual-data-nodes=ds0.t_order_info_$->{0..2}
# 以id作为算法的数据值
spring.shardingsphere.rules.sharding.tables.t_order_info.table-strategy.standard.sharding-column=id
# 指定分片算法名称(自定义的名称)
spring.shardingsphere.rules.sharding.tables.t_order_info.table-strategy.standard.sharding-algorithm-name=order-inline

3.生成代码

使用代码生成器生成代码,订单实体OrderInfo如下,其中自己创建了一个@Sharding 注解用来标识分片键,这里根据id值来分表。

@Getter
@Setter
@ToString
@NoArgsConstructor
public class OrderInfo {

	/** 主键ID**/
	@Sharding
    @JsonFormat(shape = Shape.STRING)
	private Long id;

	/** 订单编码**/
	private String orderCode;

	/** 订单时间**/
	private Date orderDate;

	/** 订单状态**/
	private Integer orderStatus;
}

@Sharding 注解内容如下

/**
 * 数据分片
 */
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Sharding {

    /**
     * 要识别的信息
     */
    String name() default "";

}

OrderInfoMapper.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.cdw.modules.order.mapper.OrderInfoMapper">

	<resultMap id="OrderInfoMap" type="OrderInfo">
		<id column="id" property="id"/>
	</resultMap>

	<sql id="baseColumns">
		 o.id,
		 o.order_code,
		 o.order_date,
		 o.stop_time,
		 o.order_status
	</sql>

	<!-- 根据主键ID查询 -->
	<select id="findById" resultMap="OrderInfoMap">
		SELECT
		 <include refid="baseColumns" />
		FROM t_order_info o WHERE o.id = #{id}
	</select>

	<!-- 列表  -->
	<select id="findList" resultMap="OrderInfoMap">
		SELECT
		<include refid="baseColumns" />
        FROM t_order_info o
	    <if test="id!=null">AND o.id = #{id}</if>
        <if test="orderCode!=null and orderCode!=''">AND o.order_code LIKE concat('%', #{orderCode}, '%')</if>
        <if test="orderDate!=null">AND o.order_date = #{orderDate}</if>
        <if test="orderStatus!=null">AND o.order_status = #{orderStatus}</if>
        
	</select>

	<!-- 批量插入记录 -->
	<insert id="insertBatch" useGeneratedKeys="true" keyProperty="id" parameterType="OrderInfo">
		INSERT INTO t_order_info (
		 id,
		 order_code,
		 order_date,
		 order_status
		) VALUES
		<foreach  item="emp" separator="," collection="list">
		(
		 #{emp.id},
		 #{emp.orderCode},
		 #{emp.orderDate},
		 #{emp.orderStatus}
		)
	    </foreach>
	</insert>

	<!-- 批量更新记录 -->
	<update id="updateBatch">
		<foreach  item="emp" separator=";" collection="list">
		UPDATE t_order_info
		<set>
			<if test="emp.orderCode!=null and emp.orderCode!=''">order_code = #{emp.orderCode},</if>
			<if test="emp.orderDate!=null">order_date = #{emp.orderDate},</if>
			<if test="emp.orderStatus!=null">order_status = #{emp.orderStatus},</if>
		</set>
		WHERE id = #{emp.id}
		</foreach>
	</update>

	<!-- 批量删除记录 -->
	<delete id="delByIds" parameterType = "java.util.List">
		DELETE FROM t_order_info WHERE
	    id in
		<foreach collection="list"  item="item" open="(" separator="," close=")"  >
		    #{item}
		 </foreach>
	</delete>

</mapper>

OrderInfoService 代码

/**
 * 获取基础订单信息分页列表
 *
 * @author lockie
 * @date: 2022-8-10 09:20
 * @param: param
 */
public PageInfo<OrderInfoVO> getOrderInfoPage(OrderInfo param) {
	param.enablePage();
	return toPageInfo(orderInfoMapper.findList(param), OrderInfoVO.class);
}

/** 查询列表**/
public List<OrderInfoVO> getOrderInfoList(OrderInfo param) {
	 return copyToList(orderInfoMapper.findList(param), OrderInfoVO.class);
}

/** 根据ID获取详情**/
public OrderInfoVO getOrderInfoById(Long id) {
	return orderInfoMapper.findById(id);
}

/** 新增**/
public Integer insertOrderInfo(OrderInfo orderInfo) {
	return insertBatchOrderInfo(Collections.singletonList(orderInfo));
}

/** 批量新增**/
public Integer insertBatchOrderInfo(List<OrderInfo> orderInfoList) {
	if (CollectionUtils.isEmpty(orderInfoList)) {
		return null;
	}
	try {
		List<List<OrderInfo>> data = ShardingUtil.shardingData(shardingCount.getShardingTableCount(), orderInfoList);
		data.forEach(s -> {
			log.info("orderInfoMapper.insertBatchOrderInfo:[{}]", JsonUtil.toJson(s));
			orderInfoMapper.insertBatch(s);
		});
	} catch (Exception e) {
		throw new RuntimeException(e);
	}
	return 1;
}

其中新增操作时重要的就是新增到哪个表去,ShardingUtil就是根据id%3 取余然后找到对应的表

/**
 * 分表工具类
 * 批量操作ShardingSphere本就不支持, 因此封装这个工具用于处理该问题
 * 该工具仅仅支持取模模式
 * @author lockie
 * @date: 2022-7-30 19:09
 */
@Slf4j
public class ShardingUtil {

    private static String fieldName = "";

    private static PropertyDescriptor sourceProperty = null;

    /**
     * 根据数据大小以及分片数量得到初始化分片桶
     *
     * @author lockie
     */
    private static <T> List<List<T>> init(int listCount) {
        List<List<T>> retList = new ArrayList<>(listCount);
        // 默认先占位
        for (int i = 0; i < listCount; i++) {
            retList.add(new ArrayList<>());
        }
        return retList;
    }

    /**
     * 数据分片
     *
     * @author lockie
     * @date: 2022-9-6 14:37
     */
    public static <T> List<List<T>> shardingData(int tableCount, List<T> data) throws RuntimeException, InvocationTargetException, IllegalAccessException {
        if (tableCount == 0) {
            throw new RuntimeException("tableCount 不能为0!");
        }
        List<List<T>> retData = init(tableCount);
        // 如果是1的话, 则表示没有分表, 直接封装数据返回即可
        if (tableCount == 1) {
            retData.set(0, data);
            retData.removeIf(CollectionUtils::isEmpty);
            log.info("处理 retData 返回值3{}", JsonUtil.toJson(retData));
            return retData;
        }
    
        // 基础类型标记
        boolean isBasicType = false;
        // 获取类
        Class<?> clazz = data.get(0).getClass();
        // 判断是否为基本类型的包装类
        if (clazz.getSimpleName().equals("Long") || clazz.getSimpleName().equals("Integer")) {
            isBasicType = true;
        }
        // 标记是否有分表操作
        boolean hasAnnotation = false;
        // 获取类型
        for (T t : data) {
            Object value = null;
            if (!isBasicType) {
                // 检验字段名称是否有值, 没有的话循环一遍属性查找
                if (StringUtils.isEmpty(fieldName)) {
                    Class<?> tClass = t.getClass();
                    List<Field> fields = new ArrayList<>();
                    while (tClass != null) {
                        fields.addAll(Arrays.asList(tClass.getDeclaredFields()));
                        tClass = tClass.getSuperclass();
                    }
                    for (Field field : fields) {
                        Sharding sharding = field.getAnnotation(Sharding.class);
                        if (null == sharding) {
                            continue;
                        }
                        fieldName = field.getName();
                        hasAnnotation = true;
                        break;
                    }
                }
                // 如果没标注分表注解的话, 则默认将原始数据封装好丢出去
                if (!hasAnnotation) {
                    retData.add(data);
                    retData.removeIf(CollectionUtils::isEmpty);
                    log.info("处理 retData 返回值2{}", JsonUtil.toJson(retData));
                    return retData;
                }
    
                // 根据字段名称获取这个属性
                sourceProperty = BeanUtils.getPropertyDescriptor(clazz, fieldName);
                value = sourceProperty.getReadMethod().invoke(t);
            } else {
                value = t;
            }
            // 取模操作
            int index = (int) (Long.parseLong(String.valueOf(value)) % tableCount);
            List<T> valueList = retData.get(index);
            // 添加到对应的List中
            valueList.add(t);
        }

        // 重置状态
        sourceProperty = null;
        fieldName = "";

        // 删除空List
        retData.removeIf(CollectionUtils::isEmpty);
        log.info("处理 retData 返回值1{}", JsonUtil.toJson(retData));
        return retData;
    }
}

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

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

相关文章

链动2+1系统|购买三单就能迅速回本,链动2+1模式到底有多暴利?

链动21模式号称起步创业无泡沫&#xff0c;半个月就能盈利上百万&#xff0c;用户裂变速度更是让人瞠目结舌。那么&#xff0c;链动21模式到底有多暴利&#xff1f;其实链动21模式最关键的&#xff0c;是合理的利润分配和奖励机制&#xff0c;让消费者在购物的同时&#xff0c;…

【解决报错】‘jupyter‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件

在当前路径下使用cmd打开后&#xff0c;输入jupyter notebook出现如下错误&#xff1a; 通常可能出现的问题有两种&#xff1a; &#xff08;1&#xff09;你本身就没安装jupyter&#xff0c;如果你配置了anaconda&#xff0c;就自带jupyter&#xff0c;直接跳到问题2。如果确…

Confluence主页面更新记录停留在去年,搜索也只能搜索去年之前的数据问题解决方案

问题描述 Confluence主页最近更新页面不更新了&#xff0c;停留在之前的时间段。其次搜索也只能搜索出来停留在这个时间段之前的数据。 核心原因 索引出现问题了&#xff0c;重建索引即可。 解决办法 直接重启Confluence。 重启Confluence的姿势 描述一下我解决思路&…

28-vuex

vuex 一、vuex 专门在vue中实现集中式状态&#xff08;数据&#xff09;管理的一个Vue插件&#xff0c;对Vue应用中多个组件的共享状态进行集中式的管理&#xff08;读/写&#xff09;&#xff0c;也是一种组件间通信的方式&#xff0c;且适用于任意组件间通信。 使用场景&a…

Java 【数据结构OJ题十道】—— 二叉树篇1

文章目录一、 检查两棵二叉树是否相同二、 另一棵二叉树的子树三、 二叉树的构建及遍历四、序列化二叉树和反序列化二叉树(难)五、二叉树创建字符串六、 二叉树前序非递归遍历实现七、 二叉树中序非递归遍历实现八、 二叉树后序非递归遍历实现九、二叉搜索树中找到两个结点的最…

如何将电脑文件备份到百度网盘

如何将电脑文件备份到百度网盘&#xff1f;说到文件备份&#xff0c;很多小伙伴会将电脑文件备份到移动硬盘或者U盘里&#xff0c;移动硬盘和U盘是比较常见的存储介质&#xff0c;使用和携带起来也是非常方便&#xff0c;因此深受大家的喜欢。除此之外&#xff0c;大家可能还忽…

2023年,IT互联网还有发展前景吗?

不得不说&#xff0c;互联网在整个社会经济发展中扮演着不可或缺的角色&#xff1b;不仅自身的技术具有前沿性&#xff0c;也推动着其他行业进入数字化经济时代&#xff0c;让我们的工作生活变得更加便捷。 在“互联网”时代&#xff0c;每个服务行业都会利用大数据&#xff0…

将自带记事本替换为Notepad2【中文版,带替换文件】

Notepad2是我在寻找一个合适的代码浏览工具的时候发现的&#xff0c;当需要一个用来浏览代码的文本编辑器时候&#xff0c;需要体积小&#xff0c;速度快&#xff0c;语法高亮&#xff0c;解释度高&#xff0c;VsCode作为生产环境已经不适合作为浏览工具了。了解到Notepad2&…

《动手学习深度学习》笔记(二)线性神经网络

三、线性神经网络 3.1 线性回归 3.1.1 介绍 1. 回归是为一个或多个自变量与因变量之间的关系建模的一类方法。而线性回归基于几个简单的假设&#xff1a;① 自变量和因变量关系是线性的&#xff1b;② 允许包含噪声但是噪声遵循正态分布。   2. 训练数据集/训练集&#xff…

算法训练营 day53 动态规划 买卖股票的最佳时机系列2

算法训练营 day53 动态规划 买卖股票的最佳时机系列2 买卖股票的最佳时机III 123. 买卖股票的最佳时机 III - 力扣&#xff08;LeetCode&#xff09; 给定一个数组&#xff0c;它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。…

软件项目管理知识回顾---网络图

网络图 9.网络图 9.1简介 1.分类 AOA&#xff0c;双代号&#xff0c;ADMAON,PDM&#xff0c;单代号&#xff0c;前导图2.活动的逻辑管理 头到头/尾&#xff0c;尾到头/尾 依赖关系 3.工序 紧前紧后9.2绘制规则 1.两个节点只能一条线。不能是平行线。平行的话就不知道是哪个活动…

LeetCode-93. 复原 IP 地址

目录题目思路回溯法题目来源 93. 复原 IP 地址 题目思路 意识到这是切割问题&#xff0c;切割问题就可以使用回溯搜索法把所有可能性搜出来&#xff0c;和131.分割回文串就十分类似了。 回溯法 1.递归参数 startIndex一定是需要的&#xff0c;因为不能重复分割&#xff0c…

【GeoDjango框架解析】读取矢量数据写入postgis数据库

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 【GeoDjango框架解析】读取矢量数据写入postgis数据库 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录系列…

关于iframe一些通讯的记录(可适用工作流审批,文中有项目实践,欢迎咨询)

一.知识点(1).我们可以通过postMessage(发送方)和onmessage(接收方)这两个HTML5的方法, 来解决跨页面通信问题&#xff0c;或者通过iframe嵌套的不同页面之间的通信a.父页面代码如下<div v-if"src" class"iframe"><iframeref"iframe"id…

Kafka进阶篇-消费者详解Flume消费Kafka原理

简介 由于挺多时候如果不太熟系kafka消费者详细的话&#xff0c;很容易产生问题&#xff0c;所有剖析一定的原理很重要。 Kafka消费者图解 消费方式 消费者总体工作流程 消费者组初始化流程 消费者详细消费流程 消费者重要参数 bootstrap.servers 向 Kafka 集群建立初…

Jackson使用进阶

实现 注解 属性命名 JsonProperty 定义属性序列化时的名称。 JacksonAnnotation public interface JsonProperty {public final static String USE_DEFAULT_NAME "";public final static int INDEX_UNKNOWN -1;//指定属性的名称。String value() default USE_…

2022IDEA搭建springMvc项目

springmvc项目搭建一. 创建maven项目二. Add Framework Support三. 添加依赖并配置maven四. 配置前端控制器DispatcherServlet五. 配置SpringMVC.XML文件六. 创建controller类七. 创建index.html页面八. 查看jar包是否添加九. 配置tomcat&#xff08;重&#xff09;十. springm…

Kafka(7):生产者详解

1 消息发送 1.1 Kafka Java客户端数据生产流程解析 1 首先要构造一个 ProducerRecord 对象,该对象可以声明主题Topic、分区Partition、键 Key以及值 Value,主题和值是必须要声明的,分区和键可以不用指定。 2 调用send() 方法进行消息发送。 3 因为消息要到网络上进行传输…

国产蓝牙耳机什么便宜又好用?学生党平价蓝牙耳机推荐

蓝牙耳机凭借近几年的快速发展&#xff0c;越来越多的品牌、款式出现在人们的日常生活当中。最近看到很多人问&#xff0c;国产蓝牙耳机什么便宜又好用&#xff1f;针对这个问题&#xff0c;我来给大家推荐几款平价蓝牙耳机&#xff0c;很适合学生党&#xff0c;一起来看看吧。…

推荐系统从入门到入门(3)——基于MapReuduce与Spark的分布式推荐系统构建

本系列博客总结了不同框架、不同算法、不同界面的推荐系统&#xff0c;完整阅读需要大量时间&#xff08;又臭又长&#xff09;&#xff0c;建议根据目录选择需要的内容查看&#xff0c;欢迎讨论与指出问题。 目录 系列文章梗概 系列文章目录 三、MapReduce 1.MapReduce详…