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(后期同步更新)


    org.apache.shardingsphere
    shardingsphere-jdbc-core
    ${sharding.version}

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

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



    javax.xml.bind
    jaxb-api
    ${jaxb.version}


    com.sun.xml.bind
    jaxb-impl
    ${jaxb.version}


    org.glassfish.jaxb
    jaxb-runtime
    ${jaxb.version}

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

/**
 * 高版本 snakeyaml 在 spring-boot-autoconfigure 加载配置文件时报错
 * java.lang.NoSuchMethodError: org.yaml.snakeyaml.***.***: method ()V not found
 * 覆盖源码,添加无参构造函数
 * 

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

*/ public class Representer extends SafeRepresenter { protected Map, 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, 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 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 properties, Object javaBean) { List value = new ArrayList(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 t = arguments[0]; SequenceNode snode = (SequenceNode) node; Iterable memberList = Collections.emptyList(); if (object.getClass().isArray()) { memberList = Arrays.asList((Object[]) object); } else if (object instanceof Iterable) { // list memberList = (Iterable) object; } Iterator 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 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 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 getProperties(Class 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仓库地址

你可能感兴趣的:(架构之路,mybatis,spring,boot,spring,数据库)