MySQL分库分表原理

面试题:

什么是分库分表

分库分表是一种数据库优化策略

目的:为了解决由于单一的库表数据量过大而导致数据库性能降低的问题

分库:将原来独立的数据库拆分成若干数据库组成

分表:将原来的大表(存储近千万数据的表)拆分成若干个小表

什么时候考虑分库分表

​ 注意:当我们使用读写分离、索引、缓存后,数据库的压力还是很大的时候,这就需要将数据库进行拆分(迫不得已,最后最后的办法)。

  1. 单表出现的瓶颈:单表数据量过大,导致读写性能较慢。 关于 MySQL 单库和单表的数据量限制,和不同的服务器配置,以及不同结构的数据存储有关,并没有一个确切的数字。这里参考阿里巴巴的《Java 开发手册》中数据库部分的建表规约:单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。

  2. 单库出现的瓶颈:CPU压力过大,内存不足,磁盘空间不足,网络带宽不足,导致读写性能较慢

分库分表的实现方式

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

1.垂直分表

概念:按照字段访问频次,将一张表的字段拆分到多张表当中。

1. 拆分后表的记录行数不变

2. 拆分的表之间一般是一对一的关系

3. 每张表仅存储其中一部分字段

2.垂直分库

概念:按照业务将表进行分类,分布到不同的数据库上面。

核心理念:专库专用

注意:它需要解决跨库带来的所有复杂问题(例如:事务一致性问题)。

3.水平分表

概念:整张表在数据结构不变的情况下,将大表按一定规则拆分成多个小表

1. 每个表的结构一样;

2. 每个表的数据不一样;

3. 所有表的数据并集为全量数据;

算法说明:

如果商品ID为偶数,将此操作映射至商品信息1表;

如果商品ID为奇数,将操作映射至商品信息2表。

此操作要访问表名称的表达式为商品信息[商品ID%2 + 1];

4.水平分库

概念:将表水平切分后 按某个字段的某种规则分到不同的数据库,使得每个库具有相同的表,表中的数据不相同,水平分库一般是伴随水平分表。

场景:

假如当前有8w店铺,每个店铺平均150个不同规格的商品,那商品数量得往1200w+上预估,并且商品库属于访问非常频繁的资源,单台服务器已经无法支撑。此时该如何优化?

目前情况是那怕再次垂直分库也无法解决数据瓶颈问题。我们可以尝试水平分库,将商品ID为奇数的和商品ID为偶数的商品信息分别放在两个不同库中;

说明:

总之,水平分库后,各个库保存的表结构是一致的,但是表中内容不一样;

最佳实践:

一般来说,在系统设计阶段就应该根据业务耦合松紧来确定垂直分库,垂直分表方案。当然在数据量及访问压力不是特别大的情况,首先考虑缓存、读写分离、索引技术等方案。若数据量极大,且持续增长,再考虑水平分库水平分表方案。

总之,基于开发和维护成本比考虑,非必须,不要对数据库做分库分表处理!

分库分表带来的问题(了解)

单个数据库的时候MySQL操作很简单,但是当进行分库分表之后数据库被拆分的非常细,这个时候如何操作这些表就是要面临的问题,就需要引入sharding-jdbc,来解决分库分表操作的问题,sharding-jdbc不是进行分库分表的,分库分表是已经提前分好的,sharding-jdbc是解决了如何方便的去操作这些数据库表。

  1. 分布式事务

  2. 问题:在分库分表的环境中,一个业务操作可能涉及多个数据库或表,这会导致分布式事务的问题。

  3. 解决方案ShardingSphere 支持多种分布式事务解决方案,如 XA、Seata 等,可以有效地处理跨库事务。

  4. 全局唯一 ID 生成

  5. 问题:在分库分表的情况下,需要生成全局唯一的主键 ID。

  6. 解决方案ShardingSphere 提供了多种 ID 生成策略,如 Snowflake、UUID 等,可以确保生成的 ID 在全局范围内唯一。

  7. 数据迁移和扩容

  8. 问题:随着业务的发展,可能需要对现有的分片规则进行调整或增加新的分片。

  9. 解决方案ShardingSphere 提供了灵活的数据迁移和扩容工具,支持在线数据迁移和重新分片。

  10. 查询路由

  11. 问题:查询请求需要被正确地路由到相应的分片上。

  12. 解决方案ShardingSphere 内置了强大的 SQL 解析和路由引擎,能够自动解析 SQL 并将其路由到正确的分片。

  13. 聚合查询

  14. 问题:跨分片的聚合查询(如 GROUP BY, ORDER BY)会变得复杂。

  15. 解决方案ShardingSphere 支持跨分片的聚合查询,并通过内存计算或中间结果集合并来实现高效的聚合。

  16. 读写分离

  17. 问题:为了进一步提高性能,可能需要实现读写分离。

  18. 解决方案ShardingSphere 支持读写分离,可以将读操作路由到从库,写操作路由到主库。

  19. 配置管理

  20. 问题:分库分表的配置较为复杂,需要管理和维护。

  21. 解决方案ShardingSphere 提供了灵活的配置方式,支持 YAML、Spring Boot 配置文件等,方便管理和维护。

  22. 数据一致性

  23. 问题:在分库分表的情况下,数据的一致性难以保证。

  24. 解决方案ShardingSphere 提供了多种一致性保证机制,如最终一致性、强一致性等,可以根据业务需求选择合适的方案。

  25. 监控和运维

  26. 问题:分库分表后的系统监控和运维变得更加复杂。

  27. 解决方案ShardingSphere 提供了丰富的监控和日志功能,支持与 Prometheus、Grafana 等监控系统集成,便于运维。

ShardingSphere 是一个非常强大的框架,能够解决分库分表带来的大部分问题。它提供了全面的解决方案,包括分布式事务、全局唯一 ID 生成、数据迁移和扩容、查询路由、聚合查询、读写分离、配置管理、监控和运维等。虽然 ShardingSphere 可以解决很多问题,但在实际应用中,还需要根据具体的业务需求和场景进行适当的配置和调优。此外,对于一些非常复杂的业务逻辑和特定需求,可能仍需要额外的定制化开发。

Sharding-JDBC入门

1、ShardingSphere生态简介

​ Apache ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成,既能够独立部署,又支持混合部署配合使用的产品组成。

功能特性:它们均提供标准化的数据水平扩展分布式事务分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

概览 :: ShardingSphere (apache.org) 

文档地址:https://shardingsphere.apache.org/document/legacy/4.x/document/cn/overview

本教程主要介绍:Sharding-JDBC

Sharding-JDBC 最早是当当网内部使用的一款分库分表框架,到2017年的时候才开始对外开源,这几年在大量社区贡献者的不断迭代下,功能也逐渐完善,现已更名为 ShardingSphere,2020年4⽉16⽇正式成为 Apache 软件基⾦会的顶级项⽬。

​ Sharding-jdbc是ShardingSphere的其中一个模块,定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架

<dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
</dependency>

  • 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。

  • 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。

  • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。

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

数据分片:把表和数据库进行拆分

读写分离:把数据库分成主从两个数据库,主数据库负责写,从数据库负责读

2、sharding-jdbc相关名词解释

参考官网-核心概念

  • 逻辑表(LogicTable):(面向开发,实际上是不存在的表)进行水平拆分的时候同一类型(逻辑、数据结构相同)的表的总称。

  • 真实表(ActualTable):(真实存在的表,真实操作的表)在分片的数据库中真实存在的物理表。

  • 数据节点(DataNode):(描述的是哪个数据源下的哪张表,相当与地理位置的定位)数据分片的最小单元。由数据源名称和数据表组成,例:order_db.t_order_1, order_db.t_order_2,order_db.t_order_3 说白了,具体到指定库下的指定表就是一个数据节点;

  • 动态表(DynamicTable):(属于真实表,随某种规则动态产生)逻辑表和物理表不一定需要在配置规则中静态配置。如,按照日期分片的场景,物理表的名称随着时间的推移会产生变化(股票流水)。

  • 广播表(公共表):(数据量较小,变动少,而且属于高频联合查询的依赖表)指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表:对数据库结构的详细描述、参数表。

  • 绑定表(BindingTable):多表之间存在关系,尽量关联的数据放到一个库中,避免跨库查询(让相同字段都参与到分库的规则,就会落到同一个库中)指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,则此两张表互为绑定表关系。

  • 分片键(ShardingColumn):分片字段用于将数据库(表)水平拆分的字段,支持单字段及多字段分片。例如上例中的order_id。一般在业务中经常查询使用的字段会作为分片键

3、Sharding-JDBC执行原理

参考官网-内部剖析

内核剖析 :: ShardingSphere

4、sharding-jdbc分片方式介绍

参考:Spring Boot配置 :: ShardingSphere

sharding-jdbc实现数据分片有4种策略:

1.inline模式

  • 使用最简单,开发成本比较低;

  • 只能使用单个字段作为分片键;

  • 基于行表达式定义分片规则;

通过groovy表达式来表示分库分表的策略:
db0  
  ├── t_order_1
  └── t_order_2
db1
  ├── t_order_1
  └── t_order_2  
表达式:
    db${0..1}.t_order_${1..2}    通过一个表达式代表4个数据节点           
    t_order_${orderId % 2 + 1 }  表的分片规则

2.standard标准分片模式

  • 用户可通过代码自定义复杂的分片策略;

  • 同样只能使用单个字段作为分片键;

3.complex复合分片模式

  • 用于多分片键的复合分片策略(多片键)

4.Hint强制分片模式

  • 不指定片键,通过代码动态指定路由规则

  • 强制分片策略(强制路由)

分库分表实战.md

官网

首先我们先进入官网:

Sharding Sphere

Apache ShardingSphere

【文档】—>【遗留】

进去我们就可以看见它的版本,这里我没有用最新版本,用的是4.X版本,4.X.X的版本和5.X.X的版本核心API变化不大,但是细节上是有改动的,具体可以看官方文档,这里以4.x版本为例,其他的版本就不做过多的赘述,有兴趣可以自行了解

进入到4.x版本内后,根据下图找到我们的Spring Boot配置,里面有很多核心特性。如读写分离、数据脱敏等。。。

【用户手册】—>【Sharding-JDBC】—>【配置手册】—>【Spring Boot配置】

进入里面,我们首先找到配置,如下图。这里的配置都有注释


新建项目

这里用的是jDK1.8

直接创建就好了

进去之后,记得更改一下编码格式,防止配置乱码

【File】—>【Settings】—>【File Encodings】

将以下改为UTF-8格式

引入依赖

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath></relativePath>
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.12.RELEASE</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <!--引入sharding依赖-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <!--连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.3.12.RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

实体类

TOrder.java

package com.by.entity;

import java.io.Serializable;
import java.math.BigDecimal;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 *
 * @TableName t_order
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TOrder implements Serializable {
    /**
     * 订单id
     */
    private Long orderId;

    /**
     * 订单价格
     */
    private BigDecimal price;

    /**01
     * 下单用户id
     */
    private Long userId;

    /**
     * 订单状态
     */
    private String status;

    private static final long serialVersionUID = 1L;
}

TDict.java

package com.by.entity;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 *
 * @TableName t_dict
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TDict implements Serializable {
    /**
     * 字典id
     */
    private Long dictId;

    /**
     * 字典类型
     */
    private String type;

    /**
     * 字典编码
     */
    private String code;

    /**
     * 字典值
     */
    private String value;

    private static final long serialVersionUID = 1L;
}

TUser.java

package com.by.entity;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 *
 * @TableName t_user
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TUser implements Serializable {
    /**
     * 用户id
     */
    private Long userId;

    /**
     * 用户姓名
     */
    private String fullname;

    /**
     * 用户类型
     */
    private String userType;

    private static final long serialVersionUID = 1L;
}

TbLog

package com.by.entity;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 *
 * @TableName tb_log
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TbLog implements Serializable {
    /**
     *
     */
    private Long id;

    /**
     *
     */
    private String info;

    private static final long serialVersionUID = 1L;
}

dao层

TOrderDao.java

package com.by.dao;

import com.by.entity.TOrder;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * @Entity com.by.pojo.TOrder
 */
@Mapper
public interface TOrderDao {

    int deleteByPrimaryKey(Long id);

    int insert(TOrder record);

    int insertSelective(TOrder record);

    TOrder selectByPrimaryKey(Long id);

    int updateByPrimaryKeySelective(TOrder record);

    int updateByPrimaryKey(TOrder record);

    /**
     * 根据用户id精准查询,获取订单集合
     * @param userId
     * @return
     */
    List<TOrder> findByUserId(Long userId);

    /**
     * 根据用户id范围查询对应的订单集合信息
     * @param start
     * @param end
     * @return
     */
    List<TOrder> findRangeByUserId(@Param("start") Long start, @Param("end") Long end);

    /**
     * 根据订单id范围查询
     * @param start
     * @param end
     * @return
     */
    List<TOrder> selectByRange(@Param("start") Long start,@Param("end") Long end);

    /**
     * 根据用户id等值查询,并且根据订单id范围查询
     * @param userId
     * @param start
     * @param end
     * @return
     */
    List<TOrder> selectByRange2(@Param("userId") Long userId, @Param("start") Long start, @Param("end") Long end);

}

TDictDao.java

package com.by.dao;


import com.by.entity.TDict;
import org.apache.ibatis.annotations.Mapper;

/**
 * @Entity com.by.entity.TDict
 */
@Mapper
public interface TDictDao {

    int deleteByPrimaryKey(Long id);

    int insert(TDict record);

    int insertSelective(TDict record);

    TDict selectByPrimaryKey(Long id);

    int updateByPrimaryKeySelective(TDict record);

    int updateByPrimaryKey(TDict record);

}

TUserDao.java

package com.by.dao;


import com.by.entity.TUser;
import org.apache.ibatis.annotations.Mapper;

/**
 * @Entity com.by.entity.TUser
 */
@Mapper
public interface TUserDao {

    int deleteByPrimaryKey(Long id);

    int insert(TUser record);

    int insertSelective(TUser record);

    TUser selectByPrimaryKey(Long id);

    int updateByPrimaryKeySelective(TUser record);

    int updateByPrimaryKey(TUser record);

}

TbLogDao.java

package com.by.dao;


import com.by.entity.TbLog;
import org.apache.ibatis.annotations.Mapper;

/**
 * @Entity com.by.entity.TbLog
 */
@Mapper
public interface TbLogDao {

    int deleteByPrimaryKey(Long id);

    int insert(TbLog record);

    int insertSelective(TbLog record);

    TbLog selectByPrimaryKey(Long id);

    int updateByPrimaryKeySelective(TbLog record);

    int updateByPrimaryKey(TbLog record);

}

xml文件

TOrderMapper.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.by.dao.TOrderDao">

    <resultMap id="BaseResultMap" type="com.by.entity.TOrder">
            <id property="orderId" column="order_id" jdbcType="BIGINT"/>
            <result property="price" column="price" jdbcType="DECIMAL"/>
            <result property="userId" column="user_id" jdbcType="BIGINT"/>
            <result property="status" column="status" jdbcType="VARCHAR"/>
    </resultMap>

    <sql id="Base_Column_List">
        order_id,price,user_id,
        status
    </sql>

    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from t_order
        where  order_id = #{orderId,jdbcType=BIGINT}
    </select>
    <select id="findByUserId" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from t_order
        where  user_id = #{userId,jdbcType=BIGINT}
    </select>
    <select id="findRangeByUserId" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from t_order
        where  user_id between #{start} and #{end}
    </select>
    <select id="selectByRange" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from t_order
        where  order_id between #{start} and #{end}
    </select>
    <select id="selectByRange2" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from t_order
        where  order_id between #{start} and #{end} and user_id=#{userId}
    </select>

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete from t_order
        where  order_id = #{orderId,jdbcType=BIGINT}
    </delete>
    <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.by.entity.TOrder" useGeneratedKeys="true">
        insert into t_order
        ( order_id,price,user_id
        ,status)
        values (#{orderId,jdbcType=BIGINT},#{price,jdbcType=DECIMAL},#{userId,jdbcType=BIGINT}
        ,#{status,jdbcType=VARCHAR})
    </insert>
    <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.by.entity.TOrder" useGeneratedKeys="true">
        insert into t_order
        <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="orderId != null">orderId,</if>
                <if test="price != null">price,</if>
                <if test="userId != null">userId,</if>
                <if test="status != null">status,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
                <if test="orderId != null">order_id = #{orderId,jdbcType=BIGINT},</if>
                <if test="price != null">price = #{price,jdbcType=DECIMAL},</if>
                <if test="userId != null">user_id = #{userId,jdbcType=BIGINT},</if>
                <if test="status != null">status = #{status,jdbcType=VARCHAR},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="com.by.entity.TOrder">
        update t_order
        <set>
                <if test="price != null">
                    price = #{price,jdbcType=DECIMAL},
                </if>
                <if test="userId != null">
                    user_id = #{userId,jdbcType=BIGINT},
                </if>
                <if test="status != null">
                    status = #{status,jdbcType=VARCHAR},
                </if>
        </set>
        where   order_id = #{orderId,jdbcType=BIGINT}
    </update>
    <update id="updateByPrimaryKey" parameterType="com.by.entity.TOrder">
        update t_order
        set
            price =  #{price,jdbcType=DECIMAL},
            user_id =  #{userId,jdbcType=BIGINT},
            status =  #{status,jdbcType=VARCHAR}
        where   order_id = #{orderId,jdbcType=BIGINT}
    </update>
</mapper>

TDictMapper.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.by.dao.TDictDao">

    <resultMap id="BaseResultMap" type="com.by.entity.TDict">
        <id property="dictId" column="dict_id" jdbcType="BIGINT"/>
        <result property="type" column="type" jdbcType="VARCHAR"/>
        <result property="code" column="code" jdbcType="VARCHAR"/>
        <result property="value" column="value" jdbcType="VARCHAR"/>
    </resultMap>

    <sql id="Base_Column_List">
        dict_id,type,code,
        value
    </sql>

    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from t_dict
        where  dict_id = #{dictId,jdbcType=BIGINT}
    </select>

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete from t_dict
        where  dict_id = #{dictId,jdbcType=BIGINT}
    </delete>
    <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.by.entity.TDict" useGeneratedKeys="true">
        insert into t_dict
        ( dict_id,type,code
        ,value)
        values (#{dictId,jdbcType=BIGINT},#{type,jdbcType=VARCHAR},#{code,jdbcType=VARCHAR}
               ,#{value,jdbcType=VARCHAR})
    </insert>
    <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.by.entity.TDict" useGeneratedKeys="true">
        insert into t_dict
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="dictId != null">dictId,</if>
            <if test="type != null">type,</if>
            <if test="code != null">code,</if>
            <if test="value != null">value,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="dictId != null">dict_id = #{dictId,jdbcType=BIGINT},</if>
            <if test="type != null">type = #{type,jdbcType=VARCHAR},</if>
            <if test="code != null">code = #{code,jdbcType=VARCHAR},</if>
            <if test="value != null">value = #{value,jdbcType=VARCHAR},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="com.by.entity.TDict">
        update t_dict
        <set>
            <if test="type != null">
                type = #{type,jdbcType=VARCHAR},
            </if>
            <if test="code != null">
                code = #{code,jdbcType=VARCHAR},
            </if>
            <if test="value != null">
                value = #{value,jdbcType=VARCHAR},
            </if>
        </set>
        where   dict_id = #{dictId,jdbcType=BIGINT}
    </update>
    <update id="updateByPrimaryKey" parameterType="com.by.entity.TDict">
        update t_dict
        set
            type =  #{type,jdbcType=VARCHAR},
            code =  #{code,jdbcType=VARCHAR},
            value =  #{value,jdbcType=VARCHAR}
        where   dict_id = #{dictId,jdbcType=BIGINT}
    </update>
</mapper>

TUserMapper.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.by.dao.TUserDao">

    <resultMap id="BaseResultMap" type="com.by.entity.TUser">
            <id property="userId" column="user_id" jdbcType="BIGINT"/>
            <result property="fullname" column="fullname" jdbcType="VARCHAR"/>
            <result property="userType" column="user_type" jdbcType="CHAR"/>
    </resultMap>

    <sql id="Base_Column_List">
        user_id,fullname,user_type
    </sql>

    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from t_user
        where  user_id = #{userId,jdbcType=BIGINT} 
    </select>

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete from t_user
        where  user_id = #{userId,jdbcType=BIGINT} 
    </delete>
    <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.by.entity.TUser" useGeneratedKeys="true">
        insert into t_user
        ( user_id,fullname,user_type
        )
        values (#{userId,jdbcType=BIGINT},#{fullname,jdbcType=VARCHAR},#{userType,jdbcType=CHAR}
        )
    </insert>
    <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.by.entity.TUser" useGeneratedKeys="true">
        insert into t_user
        <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="userId != null">userId,</if>
                <if test="fullname != null">fullname,</if>
                <if test="userType != null">userType,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
                <if test="userId != null">user_id = #{userId,jdbcType=BIGINT},</if>
                <if test="fullname != null">fullname = #{fullname,jdbcType=VARCHAR},</if>
                <if test="userType != null">user_type = #{userType,jdbcType=CHAR},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="com.by.entity.TUser">
        update t_user
        <set>
                <if test="fullname != null">
                    fullname = #{fullname,jdbcType=VARCHAR},
                </if>
                <if test="userType != null">
                    user_type = #{userType,jdbcType=CHAR},
                </if>
        </set>
        where   user_id = #{userId,jdbcType=BIGINT} 
    </update>
    <update id="updateByPrimaryKey" parameterType="com.by.entity.TUser">
        update t_user
        set 
            fullname =  #{fullname,jdbcType=VARCHAR},
            user_type =  #{userType,jdbcType=CHAR}
        where   user_id = #{userId,jdbcType=BIGINT} 
    </update>
</mapper>

TbLogMapper.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.by.dao.TbLogDao">

    <resultMap id="BaseResultMap" type="com.by.entity.TbLog">
            <id property="id" column="id" jdbcType="BIGINT"/>
            <result property="info" column="info" jdbcType="VARCHAR"/>
    </resultMap>

    <sql id="Base_Column_List">
        id,info
    </sql>

    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from tb_log
        where  id = #{id,jdbcType=BIGINT} 
    </select>

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete from tb_log
        where  id = #{id,jdbcType=BIGINT} 
    </delete>
    <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.by.entity.TbLog" useGeneratedKeys="true">
        insert into tb_log
        ( id,info)
        values (#{id,jdbcType=BIGINT},#{info,jdbcType=VARCHAR})
    </insert>
    <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.by.entity.TbLog" useGeneratedKeys="true">
        insert into tb_log
        <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="id != null">id,</if>
                <if test="info != null">info,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
                <if test="id != null">id = #{id,jdbcType=BIGINT},</if>
                <if test="info != null">info = #{info,jdbcType=VARCHAR},</if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="com.by.entity.TbLog">
        update tb_log
        <set>
                <if test="info != null">
                    info = #{info,jdbcType=VARCHAR},
                </if>
        </set>
        where   id = #{id,jdbcType=BIGINT} 
    </update>
    <update id="updateByPrimaryKey" parameterType="com.by.entity.TbLog">
        update tb_log
        set 
            info =  #{info,jdbcType=VARCHAR}
        where   id = #{id,jdbcType=BIGINT} 
    </update>
</mapper>

上面的实体类和dao层,以及xml文件直接复制就行,重点不在这些,重点在配置文件

基于inline模式实现库内水平分表

SQL准备

新建库、表

order_db_1
  ├── t_order_1
  └── t_order_2

#创建数据库
CREATE DATABASE `order_db_1` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
#建表
USE order_db_1;
DROP TABLE IF EXISTS `t_order_1`;
CREATE TABLE `t_order_1` (
    `order_id` BIGINT (20) NOT NULL COMMENT '订单id',
    `price` DECIMAL (10, 2) NOT NULL COMMENT '订单价格',
    `user_id` BIGINT (20) NOT NULL COMMENT '下单用户id',
    `status` VARCHAR (50) CHARACTER
SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单状态',
 PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = INNODB CHARACTER
SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

DROP TABLE IF EXISTS `t_order_2`;
CREATE TABLE `t_order_2` (
    `order_id` BIGINT (20) NOT NULL COMMENT '订单id',
    `price` DECIMAL (10, 2) NOT NULL COMMENT '订单价格',
    `user_id` BIGINT (20) NOT NULL COMMENT '下单用户id',
    `status` VARCHAR (50) CHARACTER
SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单状态',
 PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = INNODB CHARACTER
SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

配置文件

首先在application.properties文件指定Mybatis的Mapper文件

#下面内容是为了让mybatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*.xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=com.by.sharding.entity

因为我们要使用Sharding JDBC进行分库分表,所以不能用spring的数据源,接下来我们要配置Sharding JDBC

遵循以下配置数据源:

配置指定表的数据节点(也就是指定操作的逻辑表属于哪个库的哪个表)

指定库的分片策略

指定表的分片策略

因为我们接下来要根据不同的案例配置多个配置文件,为了防止配置文件太乱,直观一点,我采用的是多环境配置,这样只需要在application.properties中激活指定的配置文件即可。

库内水平分表案例配置文件application-test1.properties

#1.配置数据源
#数据源名称,多数据源以逗号分隔
spring.shardingsphere.datasource.names= ds1

#数据库连接池类名称
spring.shardingsphere.datasource.ds1.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.ds1.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.ds1.url= jdbc:mysql://localhost:端口号/order_db_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.ds1.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.ds1.password= 数据库密码
#数据库连接池的其它属性
#spring.shardingsphere.datasource.ds1.xxx=


#2.配置指定表的数据节点(指定操作的逻辑表是属于哪个库的哪张表) t_order ---->  t_order_1 、 t_order_2
#由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。
# 缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表
# (即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况

#<logic-table-name>  逻辑表的名字
#actual-data-nodes 真实数据节点
#此次没有跨库分表
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes= ds1.t_order_${1..2}


#3.指定库的分片策略(说明:因为这里只指定一个库,没有进行水平分库处理,所以无需配置分库策略配置)
#行表达式分片策略

#分片列名称
#spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column=
##分片算法行表达式,需符合groovy语法 
#spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression=

#4.指定表的分片策略(根据分片条件找到对应的真实表) 以order_id % 2 + 1来分表
#order_id 为片键
#分片列名称
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column= order_id
#分片算法行表达式,需符合groovy语法
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression= t_order_${order_id % 2 + 1}

#打印SQL
#是否开启SQL显示,默认值: false
spring.shardingsphere.props.sql.show= true


注:注意多环境配置的语法结构

application-{profile}.properties

profile:代表的就是一套环境

在`application.properties`中激活指定的配置文件语法为:spring.profiles.active={profile}

测试类

package com.by;
​
import com.by.dao.TOrderDao;
import com.by.entity.TOrder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
​
import java.math.BigDecimal;
import java.util.Random;
​
@SpringBootTest
class ShardingJdbcAppTest {
    @Autowired
    private TOrderDao tOrderDao;
    /**
     * 测试库内水平分表
     */
    @Test
    public void test1() {
        int orderId = 0;
        Random random = new Random();
        for (int i = 0; i < 20; i++) {
            //保证随机生成奇数或者偶数
            orderId += random.nextInt(2) + 1;
            TOrder order = TOrder.builder().orderId(Long.valueOf(orderId)).userId(Long.valueOf(i)).status("1").price(new BigDecimal(300)).build();
            tOrderDao.insert(order);
        }
    }
}

基于inline模式实现水平分库分表

SQL准备

在order_db_1的基础上新建库、表

order_db_1
  ├── t_order_1
  └── t_order_2
order_db_2
  ├── t_order_1
  └── t_order_2  

#继续构建order_db_2数据库
CREATE DATABASE `order_db_2` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
#建表
USE order_db_2;
DROP TABLE IF EXISTS `t_order_1`;
CREATE TABLE `t_order_1` (
    `order_id` BIGINT (20) NOT NULL COMMENT '订单id',
    `price` DECIMAL (10, 2) NOT NULL COMMENT '订单价格',
    `user_id` BIGINT (20) NOT NULL COMMENT '下单用户id',
    `status` VARCHAR (50) CHARACTER
SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单状态',
 PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = INNODB CHARACTER
SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

DROP TABLE IF EXISTS `t_order_2`;
CREATE TABLE `t_order_2` (
    `order_id` BIGINT (20) NOT NULL COMMENT '订单id',
    `price` DECIMAL (10, 2) NOT NULL COMMENT '订单价格',
    `user_id` BIGINT (20) NOT NULL COMMENT '下单用户id',
    `status` VARCHAR (50) CHARACTER
SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单状态',
 PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = INNODB CHARACTER
SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

配置文件

创建application-test2.properties

#1.配置数据源①
#数据源名称,多数据源以逗号分隔
spring.shardingsphere.datasource.names= ds1,ds2

#数据库连接池类名称
spring.shardingsphere.datasource.ds1.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.ds1.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.ds1.url= jdbc:mysql://localhost:端口号/order_db_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.ds1.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.ds1.password= 数据库密码
#数据库连接池的其它属性
#spring.shardingsphere.datasource.ds1.xxx=


#1.配置数据源②

#数据库连接池类名称
spring.shardingsphere.datasource.ds2.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.ds2.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.ds2.url= jdbc:mysql://localhost:端口号/order_db_2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.ds2.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.ds2.password= 数据库密码

#2.配置指定表的数据节点(指定操作的逻辑表是属于哪个库的哪张表) t_order ---->  t_order_1 、 t_order_2
#由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。
# 缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表
# (即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况

#<logic-table-name>  逻辑表的名字
#actual-data-nodes 真实数据节点
#此次没有跨库分表
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes= ds${1..2}.t_order_${1..2}


#3.指定库的分片策略(说明:因为这里只指定一个库,没有进行水平分库处理,所以无需配置分库策略配置)
#行表达式分片策略

#分片列名称(说明:找库仍然通过order_id? 换个思路,通过user_id)
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column= user_id
#分片算法行表达式,需符合groovy语法
#说明:此时分片表达式不可以写成t_order_${order_id % 2 + 1},
# 因为这样会造成数据倾斜,因为如果订单id为偶数,
# 那么所有偶数订单都会落在ds1.t_order_1,奇数订单落在ds2.t_order_2,
# 导致偶数订单和奇数订单的数据不均匀,从而导致数据倾斜。
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression= ds${user_id % 2 + 1}

#4.指定表的分片策略(根据分片条件找到对应的真实表) 以order_id % 2 + 1来分表
#order_id 为片键
#分片列名称
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column= order_id
#分片算法行表达式,需符合groovy语法
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression= t_order_${order_id % 2 + 1}

#打印SQL
#是否开启SQL显示,默认值: false
spring.shardingsphere.props.sql.show= true

注:写的时候记得在application.properties更改要激活的配置环境

测试类

 /**
     * 测试水平分库分表
     */
    @Test
    public void test2() {
        int orderId=0;
        int userId=0;
        Random random = new Random();
        for (int i = 0; i < 40; i++) {
            //保证随机生成奇数或者偶数
            orderId+=random.nextInt(2)+1;
            userId+=random.nextInt(2)+1;
            TOrder order = TOrder.builder().orderId(Long.valueOf(orderId))
                    .userId(Long.valueOf(userId))
                    .status("1")
                    .price(new BigDecimal(300))
                    .build();
            tOrderDao.insert(order);
        }
    }

sharding-JDBC广播表

广播表介绍

  • 广播表属于数据库中数据量较小和变动较少,且存在高频联合查询的表,例如:数据字典表、状态表、类别表、配置表等属于广播表。

  • 可以将这些类表在每个数据库都保存一份,所有更新操作都同时发送到所有分库执行。

SQL准备

广播表结构如下:

order_db_1
  ├── t_order_1
  └── t_order_2
  └── t_dict
order_db_2
  ├── t_order_1
  └── t_order_2  
  └── t_dict  

#在数据库 user_db、order_db_1、order_db_2中均要建表
CREATE TABLE `t_dict` (
 `dict_id` BIGINT (20) NOT NULL COMMENT '字典id',
 `type` VARCHAR (50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典类型',
 `code` VARCHAR (50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典编码',
 `value` VARCHAR (50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典值',
 PRIMARY KEY (`dict_id`) USING BTREE
) ENGINE = INNODB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

注:因为是广播表,所以需要在我们建的两个数据库内均要执行该sql

配置文件

广播表配置文件

复制application-test2.properties文件并添加以下配置语句:

spring.shardingsphere.sharding.broadcast-tables= t_dict

Plain Text

完整配置如下:application-test3.properties

#1.配置数据源①
#数据源名称,多数据源以逗号分隔
spring.shardingsphere.datasource.names= ds1,ds2

#数据库连接池类名称
spring.shardingsphere.datasource.ds1.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.ds1.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.ds1.url= jdbc:mysql://localhost:端口号/order_db_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.ds1.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.ds1.password= 数据库密码
#数据库连接池的其它属性
#spring.shardingsphere.datasource.ds1.xxx=


#1.配置数据源②

#数据库连接池类名称
spring.shardingsphere.datasource.ds2.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.ds2.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.ds2.url= jdbc:mysql://localhost:端口号/order_db_2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.ds2.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.ds2.password= 数据库密码

#2.配置指定表的数据节点(指定操作的逻辑表是属于哪个库的哪张表) t_order ---->  t_order_1 、 t_order_2
#由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。
# 缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表
# (即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况

#<logic-table-name>  逻辑表的名字
#actual-data-nodes 真实数据节点
#此次没有跨库分表
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes= ds${1..2}.t_order_${1..2}


#3.指定库的分片策略(说明:因为这里只指定一个库,没有进行水平分库处理,所以无需配置分库策略配置)
#行表达式分片策略

#分片列名称(说明:找库仍然通过order_id? 换个思路,通过user_id)
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column= user_id
#分片算法行表达式,需符合groovy语法
#说明:此时分片表达式不可以写成t_order_${order_id % 2 + 1},
# 因为这样会造成数据倾斜,因为如果订单id为偶数,
# 那么所有偶数订单都会落在ds1.t_order_1,奇数订单落在ds2.t_order_2,
# 导致偶数订单和奇数订单的数据不均匀,从而导致数据倾斜。
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression= ds${user_id % 2 + 1}

#4.指定表的分片策略(根据分片条件找到对应的真实表) 以order_id % 2 + 1来分表
#order_id 为片键
#分片列名称
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column= order_id
#分片算法行表达式,需符合groovy语法
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression= t_order_${order_id % 2 + 1}


#配置广播表(两种方式)
#①第一种:像数组形式配置
#spring.shardingsphere.sharding.broadcast-tables[0]= 第一个广播表名
#spring.shardingsphere.sharding.broadcast-tables[1]= 第二个广播表名
#spring.shardingsphere.sharding.broadcast-tables[X]= 第X个广播表名

#②第二种:像字符串形式配置,多个广播表以逗号分隔(说明这里我们只写了一个广播表,所以只写一个就行)
spring.shardingsphere.sharding.broadcast-tables= t_dict

#打印SQL
#是否开启SQL显示,默认值: false
spring.shardingsphere.props.sql.show= true

注:仍然不要忘记在在application.properties更改要激活的配置环境哦

测试类

   @Autowired
    private TDictMapper tDictMapper;

    @Test
    public void commonTable(){
        TDict build = TDict.builder().dictId(1l).code("666").type("1").value("888")
                .build();
        tDictMapper.insert(build);
    }

基于inline模式实现垂直分库

SQL准备

数据库结构如下:

order_db_1
  ├── t_order_1
  └── t_order_2
  └── t_dict
order_db_2
  ├── t_order_1
  └── t_order_2  
  └── t_dict  
user_db
  └── t_user

#创建数据库
CREATE DATABASE `user_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
#建表
USE user_db;
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
 `user_id` BIGINT (20) NOT NULL COMMENT '用户id',
 `fullname` VARCHAR (255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户姓名',
 `user_type` CHAR (1) DEFAULT NULL COMMENT '用户类型',
 PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = INNODB CHARACTER
SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

配置文件

垂直分库配置文件

复制application-test3.properties文件,并添加添加以下配置语句:

#  配置数据源③
#数据库连接池类名称
spring.shardingsphere.datasource.udb.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.udb.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.udb.url= jdbc:mysql://localhost:端口号/user_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.ds2.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.ds2.password= 数据库密码


#针对t_user表属于一个库下的,没有进行分表,所以逻辑表和真实数据节点一致,也不需要配置任何分片策略
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes= udb.t_user

完整配置如下:application-test4.properties

#1.配置数据源①
#数据源名称,多数据源以逗号分隔
spring.shardingsphere.datasource.names= ds1,ds2,udb,defdb

#数据库连接池类名称
spring.shardingsphere.datasource.ds1.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.ds1.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.ds1.url= jdbc:mysql://localhost:端口号/order_db_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.ds1.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.ds1.password= 数据库密码
#数据库连接池的其它属性
#spring.shardingsphere.datasource.ds1.xxx=


#  配置数据源②
#数据库连接池类名称
spring.shardingsphere.datasource.ds2.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.ds2.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.ds2.url= jdbc:mysql://localhost:端口号/order_db_2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.ds2.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.ds2.password= 数据库密码



#  配置数据源③
#数据库连接池类名称
spring.shardingsphere.datasource.udb.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.udb.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.udb.url= jdbc:mysql://localhost:端口号/user_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.udb.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.udb.password= 数据库密码


#  配置数据源④

#数据库连接池类名称
spring.shardingsphere.datasource.defdb.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.defdb.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.defdb.url= jdbc:mysql://localhost:端口号/default_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.defdb.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.defdb.password= 数据库密码


#2.配置指定表的数据节点(指定操作的逻辑表是属于哪个库的哪张表) t_order ---->  t_order_1 、 t_order_2
#由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。
# 缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表
# (即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况

#<logic-table-name>  逻辑表的名字
#actual-data-nodes 真实数据节点
#此次没有跨库分表
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes= ds${1..2}.t_order_${1..2}
#针对t_user表属于一个库下的,没有进行分表,所以逻辑表和真实数据节点一致,也不需要配置任何分片策略
#----->针对垂直分库,我们的目的是专库专用,有多少个库就配置多少个数据源即可
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes= udb.t_user


#3.指定库的分片策略(说明:若只指定一个库,则不需要进行水平分库处理,所以无需配置分库策略配置)
#行表达式分片策略

#分片列名称(说明:找库仍然通过order_id? 换个思路,通过user_id)
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column= user_id
#分片算法行表达式,需符合groovy语法
#说明:此时分片表达式不可以写成t_order_${order_id % 2 + 1},
# 因为这样会造成数据倾斜,因为如果订单id为偶数,
# 那么所有偶数订单都会落在ds1.t_order_1,奇数订单落在ds2.t_order_2,
# 导致偶数订单和奇数订单的数据不均匀,从而导致数据倾斜。
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression= ds${user_id % 2 + 1}

#4.指定表的分片策略(根据分片条件找到对应的真实表) 以order_id % 2 + 1来分表
#order_id 为片键
#分片列名称
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column= order_id
#分片算法行表达式,需符合groovy语法
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression= t_order_${order_id % 2 + 1}


#配置广播表(两种方式)
#①第一种:像数组形式配置
#spring.shardingsphere.sharding.broadcast-tables[0]= 第一个广播表名
#spring.shardingsphere.sharding.broadcast-tables[1]= 第二个广播表名
#spring.shardingsphere.sharding.broadcast-tables[X]= 第X个广播表名

#②第二种:像字符串形式配置,多个广播表以逗号分隔(说明这里我们只写了一个广播表,所以只写一个就行)
spring.shardingsphere.sharding.broadcast-tables= t_dict

#打印SQL
#是否开启SQL显示,默认值: false
spring.shardingsphere.props.sql.show= true

测试类

  /**
     * 测试垂直分库
     */
    @Test
    public void test04() {
        TUser user = TUser.builder().userId(133l).userType("1")
                .fullname("laozhang").build();
        tUserDao.insert(user);
    }

默认数据源

什么是默认数据源呢?我们可以理解为如果没有配置分片策略,则可指定默认访问的数据源,也就是说只需要指定数据源,无需指定数据节点、库和表的分片策略也无需执行。

如果在相关的操作时,发现逻辑表没有对应的数据节点、库表的分片配置,那么就走指定的数据源;

SQL准备

-- 构建数据
create database default_db character set utf8;
use default_db;
-- 构建表
create table tb_log (
    id bigint primary key ,
    info varchar(30)
);

配置文件

直接在application-test4.properties文件中添加以下配置

#数据源名称,多数据源以逗号分隔
spring.shardingsphere.datasource.names= ds1,ds2,udb,defdb


#  配置数据源④

#数据库连接池类名称
spring.shardingsphere.datasource.defdb.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.defdb.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.defdb.url= jdbc:mysql://localhost:端口号/default_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.defdb.username= 数据库用户名

#数据库密码
spring.shardingsphere.datasource.defdb.password= 数据库密码


#配置默认数据源(当大量的表存在于单个库中,并且库内没有水平分表处理,可使用默认数据源)
#未配置分片规则的表将通过默认数据源定位
spring.shardingsphere.sharding.default-data-source-name=defdb

测试类

/**
     * 测试默认数据源
     * 对于没有做分片处理的操作,则会直接访问默认数据源处理
     */
    @Test
    public void test5() {
        TbLog log = TbLog.builder().id(1l).info("这是一个测试").build();
        tbLogDao.insert(log);
    }

inline模式小结

  • 优点:

  • 配置简单,开发成本低;

  • 便于维护和理解;

  • 缺点:

  • 复杂的分片策略支持不友好;

  • 不支持范围查询

Standard模式实现分库分表

官方API介绍

关于阅读官方API,我们可以发现基于标准方式实现分库分表,需要分别为库和表定义精准查询 和范围查询实现:

PreciseShardingAlgorithm :精准查询库或者表(sql使用使用了= 、in)
RangeShardingAlgorithm :范围查询库或者表 (between and 、 > 、 <)
所以,使用sharding-jdbc时尽量让分片键去查询,且遵循使用规范;

分库策略

分别实现PreciseShardingAlgorithm和RangeShardingAlgorithm 接口并实现doSharding方法

package com.by.alg;

import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;

import java.util.Collection;
import java.util.Optional;

public class Common4Db implements PreciseShardingAlgorithm<Long> , RangeShardingAlgorithm<Long> {
    //接口后面的Long类型,代表分片键的类型,我们进行分类库的分片键是user_id,所以是Long类型

    /**
     * 精准查询实现方法
     * @param dbNames 数据源名称集合,该集合数据源在配置文件中配置,如ds1,ds2,从这两个中选择一个返回
     * @param ShardingValue 封装了逻辑表、分片列名称、条件值等
     * @return
     */
    @Override
        public String doSharding(Collection<String> dbNames, PreciseShardingValue<Long> ShardingValue) {
            //inline模式下的分片算法:ds${user_id % 2 + 1}
            //获取逻辑表名称 t_order
            String logicTableName = ShardingValue.getLogicTableName();
            //获取数据库指定的分片键名称 从配置文件中获取 user_id
            String columnName = ShardingValue.getColumnName();
            //获取分片查询的条件值
            Long value = ShardingValue.getValue();
            //自定义算法:根据分片键值,获取数据库名称
            //过滤出来的数据源名称集合中,以分片键值作为后缀的,返回
            //Optional集合是jdk1.8的新特性,代表一个值存在或不存在,可以避免空指针异常
            Optional<String> result = dbNames.stream().filter(dbName -> dbName.endsWith(value % 2 + 1 + "")).findFirst();
            //判断集合中是否有元素
            if(result.isPresent())
            {
                return result.get();
            }
            return null;
    }



    /**
     * 范围查询实现方法
     * @param dbNames 数据源名称集合,该集合数据源在配置文件中配置,如ds1,ds2
     * @param ShardingValue 封装了逻辑表、分片列名称、条件值等
     * @return
     */
    @Override
    public Collection<String> doSharding(Collection<String> dbNames, RangeShardingValue<Long> ShardingValue) {
        //获取逻辑表名称 t_order
        String logicTableName = ShardingValue.getLogicTableName();
        //获取数据库指定的分片键名称 从配置文件中获取 user_id
        String columnName = ShardingValue.getColumnName();
        //获取范围值
        Range<Long> valueRange = ShardingValue.getValueRange();
        //理论上应该让范围值参与运算,然后根据某种算法规则获取数据源名称
        //hasLowerBound(),hasUpperBound() 是否存在下限和上限。返回布尔值
        if (valueRange.hasLowerBound() && valueRange.hasUpperBound()) {
            //获取下限值
            Long lower = valueRange.lowerEndpoint();
            //获取上限值
            Long upper = valueRange.upperEndpoint();
            //自定义算法:根据分片键值,获取数据库名称
            //过滤出来的数据源名称集合中,以分片键值作为后缀的,返回
        }
        return dbNames;
    }



}

分表策略

分别实现PreciseShardingAlgorithm和RangeShardingAlgorithm 接口并实现doSharding方法

package com.by.alg;

import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;

import java.util.Collection;
import java.util.Optional;

public class Common4Tb implements PreciseShardingAlgorithm<Long> , RangeShardingAlgorithm<Long> {
    //接口后面的Long类型,代表分片键的类型,我们进行分类库的分片键是user_id,所以是Long类型

    /**
     * 精准查询实现方法
     * @param tbNames 数据节点名称集合,该集合数据源在配置文件中配置,如 t_order_1,t_order_2,从这两个中选择一个返回
     * @param ShardingValue 封装了逻辑表、分片列名称、条件值等
     * @return
     */
    @Override
    public String doSharding(Collection<String> tbNames, PreciseShardingValue<Long> ShardingValue) {
        //inline模式下的分片算法:ds${user_id % 2 + 1}
        //获取逻辑表名称 t_order
        String logicTableName = ShardingValue.getLogicTableName();
        //获取数据库指定的分片键名称 从配置文件中获取 user_id
        String columnName = ShardingValue.getColumnName();
        //获取分片查询的条件值
        Long value = ShardingValue.getValue();
        //自定义算法:根据分片键值,获取数据库名称
        //过滤出来的数据源名称集合中,以分片键值作为后缀的,返回
        //Optional集合是jdk1.8的新特性,代表一个值存在或不存在,可以避免空指针异常
        Optional<String> result = tbNames.stream().filter(tbName -> tbName.endsWith(value % 2 + 1 + "")).findFirst();
        //判断集合中是否有元素
        if(result.isPresent())
        {
            return result.get();
        }
        return null;
    }



    /**
     * 范围查询实现方法
     * @param tbNames 数据节点名称集合,该集合数据源在配置文件中配置,如 t_order_1,t_order_2
     * @param ShardingValue 封装了逻辑表、分片列名称、条件值等
     * @return
     */
    @Override
    public Collection<String> doSharding(Collection<String> tbNames, RangeShardingValue<Long> ShardingValue) {
        //获取逻辑表名称 t_order
        String logicTableName = ShardingValue.getLogicTableName();
        //获取数据库指定的分片键名称 从配置文件中获取 user_id
        String columnName = ShardingValue.getColumnName();
        //获取范围值
        Range<Long> valueRange = ShardingValue.getValueRange();
        //理论上应该让范围值参与运算,然后根据某种算法规则获取数据源名称
        //hasLowerBound(),hasUpperBound() 是否存在下限和上限。返回布尔值
        if (valueRange.hasLowerBound() && valueRange.hasUpperBound()) {
            //获取下限值
            Long lower = valueRange.lowerEndpoint();
            //获取上限值
            Long upper = valueRange.upperEndpoint();
            //自定义算法:根据分片键值,获取数据库名称
            //过滤出来的数据源名称集合中,以分片键值作为后缀的,返回
        }
        return tbNames;
    }
}

配置类

#1.配置数据源①
#数据源名称,多数据源以逗号分隔
spring.shardingsphere.datasource.names= ds1,ds2,udb,defdb

#数据库连接池类名称
spring.shardingsphere.datasource.ds1.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.ds1.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.ds1.url= jdbc:mysql://localhost:端口号/order_db_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.ds1.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.ds1.password= 数据库密码
#数据库连接池的其它属性
#spring.shardingsphere.datasource.ds1.xxx=


#  配置数据源②
#数据库连接池类名称
spring.shardingsphere.datasource.ds2.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.ds2.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.ds2.url= jdbc:mysql://localhost:端口号/order_db_2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.ds2.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.ds2.password= 数据库密码



#  配置数据源③
#数据库连接池类名称
spring.shardingsphere.datasource.udb.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.udb.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.udb.url= jdbc:mysql://localhost:端口号/user_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.udb.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.udb.password= 数据库密码



#  配置数据源④
#数据库连接池类名称
spring.shardingsphere.datasource.defdb.type= com.alibaba.druid.pool.DruidDataSource
#数据库驱动类名
spring.shardingsphere.datasource.defdb.driver-class-name= com.mysql.cj.jdbc.Driver
#数据库url连接
spring.shardingsphere.datasource.defdb.url= jdbc:mysql://localhost:端口号/default_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
#数据库用户名
spring.shardingsphere.datasource.defdb.username= 数据库用户名
#数据库密码
spring.shardingsphere.datasource.defdb.password= 数据库密码



#2.配置指定表的数据节点(指定操作的逻辑表是属于哪个库的哪张表) t_order ---->  t_order_1 、 t_order_2
#由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。
# 缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表
# (即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况

#<logic-table-name>  逻辑表的名字
#actual-data-nodes 真实数据节点
#此次没有跨库分表
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes= ds${1..2}.t_order_${1..2}
#针对t_user表属于一个库下的,没有进行分表,所以逻辑表和真实数据节点一致,也不需要配置分片策略
#----->针对垂直分库,我们的目的是专库专用,有多少个库就配置多少个数据源即可
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes= udb.t_user


#3.指定库的分片策略(说明:因为这里只指定一个库,没有进行水平分库处理,所以无需配置分库策略配置)
#行表达式分片策略

#分片列名称(说明:找库仍然通过order_id? 换个思路,通过user_id)
#spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column= user_id
#分片算法行表达式,需符合groovy语法
#说明:此时分片表达式不可以写成t_order_${order_id % 2 + 1},
# 因为这样会造成数据倾斜,因为如果订单id为偶数,
# 那么所有偶数订单都会落在ds1.t_order_1,奇数订单落在ds2.t_order_2,
# 导致偶数订单和奇数订单的数据不均匀,从而导致数据倾斜。
#spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression= ds${user_id % 2 + 1}


#配置精准查询和范围查询的类
common.shard4db= com.by.alg.Common4Db
common.shard4tb= com.by.alg.Common4Tb


#用于单分片键的标准分片场景
#分片列名称
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.sharding-column= user_id
#精确分片算法类名称,用于=和IN。该类需实现PreciseShardingAlgorithm接口并提供无参数的构造器
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.precise-algorithm-class-name= ${common.shard4db}
#范围分片算法类名称,用于BETWEEN,可选。该类需实现RangeShardingAlgorithm接口并提供无参数的构造器
spring.shardingsphere.sharding.tables.t_order.database-strategy.standard.range-algorithm-class-name= ${common.shard4db}

#4.指定表的分片策略(根据分片条件找到对应的真实表) 以order_id % 2 + 1来分表
#order_id 为片键
#分片列名称
#spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column= order_id
#分片算法行表达式,需符合groovy语法
#spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression= t_order_${order_id % 2 + 1}

#分片列名称
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.sharding-column= order_id
#精确分片算法类名称,用于=和IN。该类需实现PreciseShardingAlgorithm接口并提供无参数的构造器
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.precise-algorithm-class-name= ${common.shard4tb}
#范围分片算法类名称,用于BETWEEN,可选。该类需实现RangeShardingAlgorithm接口并提供无参数的构造器
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.range-algorithm-class-name= ${common.shard4tb}

#配置广播表(两种方式)
#①第一种:像数组形式配置
#spring.shardingsphere.sharding.broadcast-tables[0]= 第一个广播表名
#spring.shardingsphere.sharding.broadcast-tables[1]= 第二个广播表名
#spring.shardingsphere.sharding.broadcast-tables[X]= 第X个广播表名

#②第二种:像字符串形式配置,多个广播表以逗号分隔(说明这里我们只写了一个广播表,所以只写一个就行)
spring.shardingsphere.sharding.broadcast-tables= t_dict

#配置默认数据源(当大量的表存在于单个库中,并且库内没有水平分表处理,可使用默认数据源)
#未配置分片规则的表将通过默认数据源定位
spring.shardingsphere.sharding.default-data-source-name=defdb

#打印SQL
#是否开启SQL显示,默认值: false
spring.shardingsphere.props.sql.show= true

测试类

 /**
     * @Description 测试数据库标准精准查询=
     */
    @Test
    public void test7(){
        List<TOrder> tOrder = tOrderDao.findByUserId(18l);
        System.out.println(tOrder);
    }

    /**
     * @Description 测试标准查询:范围匹配 between
     */
    @Test
    public void test8(){
        List<TOrder> tOrder = tOrderDao.selectByRange(18l,70l);
        System.out.println(tOrder);
    }


    /**
     * @Description 测试表的精准查询
     * 查询中没有使用user_id,则不会走自定义的精准和范围查询,而是直接全库查询
     * 但是查询时根据order_id等值查询,所以会走表的精准查询逻辑
     */
    @Test
    public void testPrecisTable(){
        TOrder tOrder = tOrderDao.selectByPrimaryKey(34l);
        System.out.println(tOrder);
    }

    /**
     * @Description 测试精准匹配表的范围查询
     */
    @Test
    public void testRangeTable(){
        List<TOrder> tOrders = tOrderDao.selectByRange(1l, 40l);
        System.out.println(tOrders);
    }

    /**
     * @Description 整体标准擦护心
     */
    @Test
    public void testAll(){
        //结论:因为user_id是等值查询,所以对于数据库调用精准查询算法类
        //因为order_id使用between ,那么会被调用表的范围查询的算法类
        List<TOrder> tOrders = tOrderDao.selectByRange2(36l, 10l, 60l);
        System.out.println(tOrders);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这孩子叫逆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值