spring-boot 整合 shardingsphere-jdbc、mybatis-plus 数据分片(文末有彩蛋)

时间:2025-03-20 10:39:33

1.什么是 ShardingSphere?

Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。

Apache ShardingSphere 设计哲学为 Database Plus,旨在构建异构数据库上层的标准和生态。

它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。 它站在数据库的上层视角,关注它们之间的协作多于数据库自身。

ShardingSphere官网

ShardingSphere-JDBC

ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。

ShardingSphere-Proxy

ShardingSphere-Proxy 定位为透明化的数据库代理端,通过实现数据库二进制协议,对异构语言提供支持。

特性 定义
数据分片 数据分片,是应对海量数据存储与计算的有效手段。ShardingSphere 基于底层数据库提供分布式数据库解决方案,可以水平扩展计算和存储。
分布式事务 事务能力,是保障数据库完整、安全的关键技术,也是数据库的核心技术。基于 XA 和 BASE 的混合事务引擎,ShardingSphere 提供在独立数据库上的分布式事务功能,保证跨数据源的数据安全。
读写分离 读写分离,是应对高压力业务访问的手段。基于对 SQL 语义理解及对底层数据库拓扑感知能力,ShardingSphere 提供灵活的读写流量拆分和读流量负载均衡。
数据迁移 数据迁移,是打通数据生态的关键能力。ShardingSphere 提供跨数据源的数据迁移能力,并可支持重分片扩展。
联邦查询 联邦查询,是面对复杂数据环境下利用数据的有效手段。ShardingSphere 提供跨数据源的复杂查询分析能力,实现跨源的数据关联与聚合。
数据加密 数据加密,是保证数据安全的基本手段。ShardingSphere 提供完整、透明、安全、低成本的数据加密解决方案。
影子库 在全链路压测场景下,ShardingSphere 支持不同工作负载下的数据隔离,避免测试数据污染生产环境。

2.整合 shardingsphere-jdbc

为方便演示,示例(水平分片-分表)使用 H2 内存数据库,直接启动项目即可,其余示例需要自行配置数据库

2.1 引入依赖

示例使用最新版本 5.4.1(后期同步更新)

<dependency>
    <groupId></groupId>
    <artifactId>shardingsphere-jdbc-core</artifactId>
    <version>${}</version>
</dependency>

这里有两个坑,网上搜一堆都是老版本整合,差距较大,问题较多;

坑一、需要引入额外依赖:

<!-- 高版本中已独立,需要单独添加依赖,否则启动报错 -->
<dependency>
    <groupId></groupId>
    <artifactId>jaxb-api</artifactId>
    <version>${}</version>
</dependency>
<dependency>
    <groupId></groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>${}</version>
</dependency>
<dependency>
    <groupId></groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>${}</version>
</dependency>

坑二、覆盖原路径重写 snakeyaml 中的类

/**
 * 高版本 snakeyaml 在 spring-boot-autoconfigure 加载配置文件时报错
 * : .***.***: method <init>()V not found
 * 覆盖源码,添加无参构造函数
 * <p>
 * public Representer() {
 * super(new DumperOptions());
 * (null, new RepresentJavaBean());
 * }
 * </p>
 */
public class Representer extends SafeRepresenter {

    protected Map<Class<? extends Object>, TypeDescription> typeDefinitions = ();

    public Representer() {
        super(new DumperOptions());
        (null, new RepresentJavaBean());
    }

    public Representer(DumperOptions options) {
        super(options);
        (null, new RepresentJavaBean());
    }

    public TypeDescription addTypeDescription(TypeDescription td) {
        if (Collections.EMPTY_MAP == typeDefinitions) {
            typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
        }
        if (() != null) {
            addClassTag((), ());
        }
        (getPropertyUtils());
        return ((), td);
    }

    @Override
    public void setPropertyUtils(PropertyUtils propertyUtils) {
        (propertyUtils);
        Collection<TypeDescription> tds = ();
        for (TypeDescription typeDescription : tds) {
            (propertyUtils);
        }
    }

    protected class RepresentJavaBean implements Represent {

        public Node representData(Object data) {
            return representJavaBean(getProperties(()), data);
        }
    }

    /**
     * Tag logic: - explicit root tag is set in serializer - if there is a predefined class tag it is
     * used - a global tag with class name is always used as tag. The JavaBean parent of the specified
     * JavaBean may set another tag (tag:,2002:map) when the property class is the same as
     * runtime class
     *
     * @param properties JavaBean getters
     * @param javaBean   instance for Node
     * @return Node to get serialized
     */
    protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
        List<NodeTuple> value = new ArrayList<NodeTuple>(());
        Tag tag;
        Tag customTag = (());
        tag = customTag != null ? customTag : new Tag(());
        // flow style will be chosen by BaseRepresenter
        MappingNode node = new MappingNode(tag, value, );
        (javaBean, node);
        FlowStyle bestStyle = ;
        for (Property property : properties) {
            Object memberValue = (javaBean);
            Tag customPropertyTag = memberValue == null ? null : (());
            NodeTuple tuple = representJavaBeanProperty(javaBean, property, memberValue, customPropertyTag);
            if (tuple == null) {
                continue;
            }
            if (!((ScalarNode) ()).isPlain()) {
                bestStyle = ;
            }
            Node nodeValue = ();
            if (!(nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).isPlain())) {
                bestStyle = ;
            }
            (tuple);
        }
        if (defaultFlowStyle != ) {
            (defaultFlowStyle);
        } else {
            (bestStyle);
        }
        return node;
    }

    /**
     * Represent one JavaBean property.
     *
     * @param javaBean      - the instance to be represented
     * @param property      - the property of the instance
     * @param propertyValue - value to be represented
     * @param customTag     - user defined Tag
     * @return NodeTuple to be used in a MappingNode. Return null to skip the property
     */
    protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
        ScalarNode nodeKey = (ScalarNode) representData(());
        // the first occurrence of the node must keep the tag
        boolean hasAlias = (propertyValue);

        Node nodeValue = representData(propertyValue);

        if (propertyValue != null && !hasAlias) {
            NodeId nodeId = ();
            if (customTag == null) {
                if (nodeId == ) {
                    // generic Enum requires the full tag
                    if (() != ) {
                        if (propertyValue instanceof Enum<?>) {
                            ();
                        }
                    }
                } else {
                    if (nodeId == ) {
                        if (() == ()) {
                            if (!(propertyValue instanceof Map<?, ?>)) {
                                if (!().equals()) {
                                    ();
                                }
                            }
                        }
                    }
                    checkGlobalTag(property, nodeValue, propertyValue);
                }
            }
        }

        return new NodeTuple(nodeKey, nodeValue);
    }

    /**
     * Remove redundant global tag for a type safe (generic) collection if it is the same as defined
     * by the JavaBean property
     *
     * @param property - JavaBean property
     * @param node     - representation of the property
     * @param object   - instance represented by the node
     */
    @SuppressWarnings("unchecked")
    protected void checkGlobalTag(Property property, Node node, Object object) {
        // Skip primitive arrays.
        if (().isArray() && ().getComponentType().isPrimitive()) {
            return;
        }

        Class<?>[] arguments = ();
        if (arguments != null) {
            if (() == ) {
                // apply map tag where class is the same
                Class<? extends Object> t = arguments[0];
                SequenceNode snode = (SequenceNode) node;
                Iterable<Object> memberList = ();
                if (().isArray()) {
                    memberList = ((Object[]) object);
                } else if (object instanceof Iterable<?>) {
                    // list
                    memberList = (Iterable<Object>) object;
                }
                Iterator<Object> iter = ();
                if (()) {
                    for (Node childNode : ()) {
                        Object member = ();
                        if (member != null) {
                            if ((())) {
                                if (() == ) {
                                    ();
                                }
                            }
                        }
                    }
                }
            } else if (object instanceof Set) {
                Class<?> t = arguments[0];
                MappingNode mnode = (MappingNode) node;
                Iterator<NodeTuple> iter = ().iterator();
                Set<?> set = (Set<?>) object;
                for (Object member : set) {
                    NodeTuple tuple = ();
                    Node keyNode = ();
                    if ((())) {
                        if (() == ) {
                            ();
                        }
                    }
                }
            } else if (object instanceof Map) { //  ends-up here
                Class<?> keyType = arguments[0];
                Class<?> valueType = arguments[1];
                MappingNode mnode = (MappingNode) node;
                for (NodeTuple tuple : ()) {
                    resetTag(keyType, ());
                    resetTag(valueType, ());
                }
            } else {
                // the type for collection entries cannot be
                // detected
            }
        }
    }

    private void resetTag(Class<? extends Object> type, Node node) {
        Tag tag = ();
        if ((type)) {
            if ((type)) {
                ();
            } else {
                ();
            }
        }
    }

    /**
     * Get JavaBean properties to be serialised. The order is respected. This method may be overridden
     * to provide custom property selection or order.
     *
     * @param type - JavaBean to inspect the properties
     * @return properties to serialise
     */
    protected Set<Property> getProperties(Class<? extends Object> type) {
        if ((type)) {
            return (type).getProperties();
        }
        return getPropertyUtils().getProperties(type);
    }
}

注意:实体类ID生成算法使用分布式雪花算法,mapper层、service层照旧即可。

@Getter
@Setter
@ToString
@Accessors(chain = true)
@TableName("tb_user")
@Schema(description = "用户")
public class User implements Serializable {

    @Serial
    private static final long serialVersionUID = 1129496589110730436L;

    @TableId(value = "id", type = IdType.ASSIGN_ID)
    @Schema(description = "主键ID")
    private Long id;

    @TableField("create_time")
    @Schema(description = "创建时间")
    private Long createTime;

    @TableField("name")
    @Schema(description = "姓名")
    private String name;

}

示例表结构(此处以水平分片-分表为例)

DROP TABLE IF EXISTS tb_user;
CREATE TABLE `tb_user`
(
    `id`          BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
    `create_time` BIGINT      DEFAULT NULL COMMENT '创建时间',
    `name`        VARCHAR(32) DEFAULT NULL COMMENT '姓名'
);

DROP TABLE IF EXISTS tb_user0;
CREATE TABLE `tb_user0`
(
    `id`          BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
    `create_time` BIGINT      DEFAULT NULL COMMENT '创建时间',
    `name`        VARCHAR(32) DEFAULT NULL COMMENT '姓名'
);

DROP TABLE IF EXISTS tb_user1;
CREATE TABLE `tb_user1`
(
    `id`          BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
    `create_time` BIGINT      DEFAULT NULL COMMENT '创建时间',
    `name`        VARCHAR(32) DEFAULT NULL COMMENT '姓名'
)

配置文件(此处以水平分片-分表为例),其他可自行配置数据源进行测试:

# 数据源配置
dataSources:
  dsA:
    dataSourceClassName: 
    driverClassName: org.
    url: jdbc:h2:mem:db_sharding
    username: sa # 用户名,用于控制台登录
    password: 123456 # 密码,用于控制台登录
  dsB0:
    dataSourceClassName: 
    driverClassName: org.
    url: jdbc:h2:mem:db_sharding
    username: sa # 用户名,用于控制台登录
    password: 123456 # 密码,用于控制台登录
  dsB1:
    dataSourceClassName: 
    driverClassName: org.
    url: jdbc:h2:mem:db_sharding
    username: sa # 用户名,用于控制台登录
    password: 123456 # 密码,用于控制台登录
# 规则配置
rules:
  - !SINGLE
    tables:
      - "*.*"
  - !SHARDING
    tables:
      # 逻辑表名
      tb_user:
        actualDataNodes: dsB0.tb_user${0..1}
        # 分库策略
        tableStrategy:
          standard:
            # 分片列名称
            shardingColumn: id
            # 分片算法名称
            shardingAlgorithmName: inline_id
    # 分片算法配置
    shardingAlgorithms:
      # 标准分片算法-行表达式分片算法
      inline_id:
        # 基于行表达式的分片算法
        type: INLINE
        props:
          algorithm-expression: tb_user${id % 2}
# 属性配置
props:
  sql-show: true

最后,启动项目

访问 http://localhost:8080/ 接口文档进行测试

访问 http://localhost:8080/h2 数据库控制台,输入用户名、密码,查看数据库信息

整合DEMO仓库地址