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

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>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core</artifactId>
    <version>${sharding.version}</version>
</dependency>

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

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

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

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

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

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

    public Representer() {
        super(new DumperOptions());
        this.representers.put(null, new RepresentJavaBean());
    }

    public Representer(DumperOptions options) {
        super(options);
        this.representers.put(null, new RepresentJavaBean());
    }

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

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

    protected class RepresentJavaBean implements Represent {

        public Node representData(Object data) {
            return representJavaBean(getProperties(data.getClass()), 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:yaml.org,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>(properties.size());
        Tag tag;
        Tag customTag = classTags.get(javaBean.getClass());
        tag = customTag != null ? customTag : new Tag(javaBean.getClass());
        // flow style will be chosen by BaseRepresenter
        MappingNode node = new MappingNode(tag, value, FlowStyle.AUTO);
        representedObjects.put(javaBean, node);
        FlowStyle bestStyle = FlowStyle.FLOW;
        for (Property property : properties) {
            Object memberValue = property.get(javaBean);
            Tag customPropertyTag = memberValue == null ? null : classTags.get(memberValue.getClass());
            NodeTuple tuple = representJavaBeanProperty(javaBean, property, memberValue, customPropertyTag);
            if (tuple == null) {
                continue;
            }
            if (!((ScalarNode) tuple.getKeyNode()).isPlain()) {
                bestStyle = FlowStyle.BLOCK;
            }
            Node nodeValue = tuple.getValueNode();
            if (!(nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).isPlain())) {
                bestStyle = FlowStyle.BLOCK;
            }
            value.add(tuple);
        }
        if (defaultFlowStyle != FlowStyle.AUTO) {
            node.setFlowStyle(defaultFlowStyle);
        } else {
            node.setFlowStyle(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(property.getName());
        // the first occurrence of the node must keep the tag
        boolean hasAlias = this.representedObjects.containsKey(propertyValue);

        Node nodeValue = representData(propertyValue);

        if (propertyValue != null && !hasAlias) {
            NodeId nodeId = nodeValue.getNodeId();
            if (customTag == null) {
                if (nodeId == NodeId.scalar) {
                    // generic Enum requires the full tag
                    if (property.getType() != Enum.class) {
                        if (propertyValue instanceof Enum<?>) {
                            nodeValue.setTag(Tag.STR);
                        }
                    }
                } else {
                    if (nodeId == NodeId.mapping) {
                        if (property.getType() == propertyValue.getClass()) {
                            if (!(propertyValue instanceof Map<?, ?>)) {
                                if (!nodeValue.getTag().equals(Tag.SET)) {
                                    nodeValue.setTag(Tag.MAP);
                                }
                            }
                        }
                    }
                    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 (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) {
            return;
        }

        Class<?>[] arguments = property.getActualTypeArguments();
        if (arguments != null) {
            if (node.getNodeId() == NodeId.sequence) {
                // apply map tag where class is the same
                Class<? extends Object> t = arguments[0];
                SequenceNode snode = (SequenceNode) node;
                Iterable<Object> memberList = Collections.emptyList();
                if (object.getClass().isArray()) {
                    memberList = Arrays.asList((Object[]) object);
                } else if (object instanceof Iterable<?>) {
                    // list
                    memberList = (Iterable<Object>) object;
                }
                Iterator<Object> iter = memberList.iterator();
                if (iter.hasNext()) {
                    for (Node childNode : snode.getValue()) {
                        Object member = iter.next();
                        if (member != null) {
                            if (t.equals(member.getClass())) {
                                if (childNode.getNodeId() == NodeId.mapping) {
                                    childNode.setTag(Tag.MAP);
                                }
                            }
                        }
                    }
                }
            } else if (object instanceof Set) {
                Class<?> t = arguments[0];
                MappingNode mnode = (MappingNode) node;
                Iterator<NodeTuple> iter = mnode.getValue().iterator();
                Set<?> set = (Set<?>) object;
                for (Object member : set) {
                    NodeTuple tuple = iter.next();
                    Node keyNode = tuple.getKeyNode();
                    if (t.equals(member.getClass())) {
                        if (keyNode.getNodeId() == NodeId.mapping) {
                            keyNode.setTag(Tag.MAP);
                        }
                    }
                }
            } else if (object instanceof Map) { // NodeId.mapping ends-up here
                Class<?> keyType = arguments[0];
                Class<?> valueType = arguments[1];
                MappingNode mnode = (MappingNode) node;
                for (NodeTuple tuple : mnode.getValue()) {
                    resetTag(keyType, tuple.getKeyNode());
                    resetTag(valueType, tuple.getValueNode());
                }
            } else {
                // the type for collection entries cannot be
                // detected
            }
        }
    }

    private void resetTag(Class<? extends Object> type, Node node) {
        Tag tag = node.getTag();
        if (tag.matches(type)) {
            if (Enum.class.isAssignableFrom(type)) {
                node.setTag(Tag.STR);
            } else {
                node.setTag(Tag.MAP);
            }
        }
    }

    /**
     * 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 (typeDefinitions.containsKey(type)) {
            return typeDefinitions.get(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: com.zaxxer.hikari.HikariDataSource
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:db_sharding
    username: sa # 用户名,用于控制台登录
    password: 123456 # 密码,用于控制台登录
  dsB0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:db_sharding
    username: sa # 用户名,用于控制台登录
    password: 123456 # 密码,用于控制台登录
  dsB1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: org.h2.Driver
    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/doc.html 接口文档进行测试

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

整合DEMO仓库地址

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
ShardingSphere:SpringBoot2+MybatisPlus+读写分离+分库分表课程目标快速的掌握读写分离+分表的实战,即插即用适用人群IT从业人员,开发人员,Java从业者,互联网从业者,性能调优人群课程简介ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈。它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成,shardingSphere定位为关系型数据库中间件。 Sharding-JDBCSharding-JDBC是Sharding-Sphere的第一个产品,也是Sharding-Sphere的前身,是当当网开源的一个产品。定位为轻量级的Java框架,在Java的JDBC层提供额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。他们均提供标准化的数据分片、读写分离、柔性事务和数据治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。Sharding-JDBC可以通过Java,YAML,Spring命名空间和Spring Boot Starter四种方式配置,开发者可根据场景选择适合的配置方式。课程特色 本章节以尽量短的时间,为使用者提供最简单的ShardingSphere的快速入门。课程说明该课程属于系列课程,分为读写分离,分库不分表,不分库分表,分库分表,读写分离+分库分表共5个回合。本课程属于其中一个回合,请各位小哥哥们注意,课程的标题哦~
课程目标快速的掌握读写分离+分表的实战,即插即用适用人群IT从业人员,开发人员,Java从业者,互联网从业者,性能调优人群课程简介ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈。它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成,shardingSphere定位为关系型数据库中间件。Sharding-JDBCSharding-JDBC是Sharding-Sphere的第一个产品,也是Sharding-Sphere的前身,是当当网开源的一个产品。定位为轻量级的Java框架,在Java的JDBC层提供额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。他们均提供标准化的数据分片、读写分离、柔性事务和数据治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。Sharding-JDBC可以通过Java,YAML,Spring命名空间和Spring Boot Starter四种方式配置,开发者可根据场景选择适合的配置方式。课程特色 本章节以尽量短的时间,为使用者提供最简单的ShardingSphere的快速入门。课程说明该课程属于系列课程,分为读写分离,分库不分表,不分库分表,分库分表,读写分离+分库分表共5个回合。本课程属于其中一个回合,请各位小哥哥们注意,课程的标题哦~
mybatis-spring-boot-starter 和 spring-boot-starter-jdbc 是用于在 Spring Boot 项目中连接数据库的两个依赖包。它们有不同的功能和用途。 mybatis-spring-boot-starter 是 MyBatis 官方提供的一个 Spring Boot Starter,它包含了使用 MyBatis 进行数据库访问所需的所有依赖。通过引入 mybatis-spring-boot-starter,您可以方便地使用 MyBatis 进行数据库操作,而无需单独引入 spring-boot-starter-jdbc 。 spring-boot-starter-jdbc 是 Spring Boot 官方提供的一个 Starter,用于支持使用 JDBC 进行数据库访问。如果您不使用 MyBatis,而只是使用 Spring 的 JdbcTemplate 进行数据库操作,那么您需要引入 spring-boot-starter-jdbc 依赖。它提供了一些必要的配置和支持,使您可以方便地使用 JDBC 进行数据库访问 。 引用的内容中提到,如果您已经引入了 mybatis-spring-boot-starter,那么您不再需要单独引入 spring-boot-starter-jdbc。这是因为 mybatis-spring-boot-starter 已经包含了 spring-boot-starter-jdbc 的依赖。这样,您就可以直接使用 MyBatis 进行数据库操作,而无需关注底层的 JDBC 配置 。 总结起来,mybatis-spring-boot-starter 是用于集成 MyBatis 的 Spring Boot Starter,而 spring-boot-starter-jdbc 是用于支持使用 JDBC 进行数据库操作的 Spring Boot Starter。如果您使用 MyBatis,建议直接引入 mybatis-spring-boot-starter,它已经包含了必要的 JDBC 依赖。如果您只是使用 Spring 的 JdbcTemplate,那么需要引入 spring-boot-starter-jdbc 。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ゞ註﹎錠oo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值