0


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

  1. <dependency>
  2. <groupId>org.apache.shardingsphere</groupId>
  3. <artifactId>shardingsphere-jdbc-core</artifactId>
  4. <version>${sharding.version}</version>
  5. </dependency>

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

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

  1. <!-- 高版本中已独立,需要单独添加依赖,否则启动报错 -->
  2. <dependency>
  3. <groupId>javax.xml.bind</groupId>
  4. <artifactId>jaxb-api</artifactId>
  5. <version>${jaxb.version}</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>com.sun.xml.bind</groupId>
  9. <artifactId>jaxb-impl</artifactId>
  10. <version>${jaxb.version}</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.glassfish.jaxb</groupId>
  14. <artifactId>jaxb-runtime</artifactId>
  15. <version>${jaxb.version}</version>
  16. </dependency>

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

  1. /**
  2. * 高版本 snakeyaml 在 spring-boot-autoconfigure 加载配置文件时报错
  3. * java.lang.NoSuchMethodError: org.yaml.snakeyaml.***.***: method <init>()V not found
  4. * 覆盖源码,添加无参构造函数
  5. * <p>
  6. * public Representer() {
  7. * super(new DumperOptions());
  8. * this.representers.put(null, new RepresentJavaBean());
  9. * }
  10. * </p>
  11. */
  12. public class Representer extends SafeRepresenter {
  13. protected Map<Class<? extends Object>, TypeDescription> typeDefinitions = Collections.emptyMap();
  14. public Representer() {
  15. super(new DumperOptions());
  16. this.representers.put(null, new RepresentJavaBean());
  17. }
  18. public Representer(DumperOptions options) {
  19. super(options);
  20. this.representers.put(null, new RepresentJavaBean());
  21. }
  22. public TypeDescription addTypeDescription(TypeDescription td) {
  23. if (Collections.EMPTY_MAP == typeDefinitions) {
  24. typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
  25. }
  26. if (td.getTag() != null) {
  27. addClassTag(td.getType(), td.getTag());
  28. }
  29. td.setPropertyUtils(getPropertyUtils());
  30. return typeDefinitions.put(td.getType(), td);
  31. }
  32. @Override
  33. public void setPropertyUtils(PropertyUtils propertyUtils) {
  34. super.setPropertyUtils(propertyUtils);
  35. Collection<TypeDescription> tds = typeDefinitions.values();
  36. for (TypeDescription typeDescription : tds) {
  37. typeDescription.setPropertyUtils(propertyUtils);
  38. }
  39. }
  40. protected class RepresentJavaBean implements Represent {
  41. public Node representData(Object data) {
  42. return representJavaBean(getProperties(data.getClass()), data);
  43. }
  44. }
  45. /**
  46. * Tag logic: - explicit root tag is set in serializer - if there is a predefined class tag it is
  47. * used - a global tag with class name is always used as tag. The JavaBean parent of the specified
  48. * JavaBean may set another tag (tag:yaml.org,2002:map) when the property class is the same as
  49. * runtime class
  50. *
  51. * @param properties JavaBean getters
  52. * @param javaBean instance for Node
  53. * @return Node to get serialized
  54. */
  55. protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
  56. List<NodeTuple> value = new ArrayList<NodeTuple>(properties.size());
  57. Tag tag;
  58. Tag customTag = classTags.get(javaBean.getClass());
  59. tag = customTag != null ? customTag : new Tag(javaBean.getClass());
  60. // flow style will be chosen by BaseRepresenter
  61. MappingNode node = new MappingNode(tag, value, FlowStyle.AUTO);
  62. representedObjects.put(javaBean, node);
  63. FlowStyle bestStyle = FlowStyle.FLOW;
  64. for (Property property : properties) {
  65. Object memberValue = property.get(javaBean);
  66. Tag customPropertyTag = memberValue == null ? null : classTags.get(memberValue.getClass());
  67. NodeTuple tuple = representJavaBeanProperty(javaBean, property, memberValue, customPropertyTag);
  68. if (tuple == null) {
  69. continue;
  70. }
  71. if (!((ScalarNode) tuple.getKeyNode()).isPlain()) {
  72. bestStyle = FlowStyle.BLOCK;
  73. }
  74. Node nodeValue = tuple.getValueNode();
  75. if (!(nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).isPlain())) {
  76. bestStyle = FlowStyle.BLOCK;
  77. }
  78. value.add(tuple);
  79. }
  80. if (defaultFlowStyle != FlowStyle.AUTO) {
  81. node.setFlowStyle(defaultFlowStyle);
  82. } else {
  83. node.setFlowStyle(bestStyle);
  84. }
  85. return node;
  86. }
  87. /**
  88. * Represent one JavaBean property.
  89. *
  90. * @param javaBean - the instance to be represented
  91. * @param property - the property of the instance
  92. * @param propertyValue - value to be represented
  93. * @param customTag - user defined Tag
  94. * @return NodeTuple to be used in a MappingNode. Return null to skip the property
  95. */
  96. protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
  97. ScalarNode nodeKey = (ScalarNode) representData(property.getName());
  98. // the first occurrence of the node must keep the tag
  99. boolean hasAlias = this.representedObjects.containsKey(propertyValue);
  100. Node nodeValue = representData(propertyValue);
  101. if (propertyValue != null && !hasAlias) {
  102. NodeId nodeId = nodeValue.getNodeId();
  103. if (customTag == null) {
  104. if (nodeId == NodeId.scalar) {
  105. // generic Enum requires the full tag
  106. if (property.getType() != Enum.class) {
  107. if (propertyValue instanceof Enum<?>) {
  108. nodeValue.setTag(Tag.STR);
  109. }
  110. }
  111. } else {
  112. if (nodeId == NodeId.mapping) {
  113. if (property.getType() == propertyValue.getClass()) {
  114. if (!(propertyValue instanceof Map<?, ?>)) {
  115. if (!nodeValue.getTag().equals(Tag.SET)) {
  116. nodeValue.setTag(Tag.MAP);
  117. }
  118. }
  119. }
  120. }
  121. checkGlobalTag(property, nodeValue, propertyValue);
  122. }
  123. }
  124. }
  125. return new NodeTuple(nodeKey, nodeValue);
  126. }
  127. /**
  128. * Remove redundant global tag for a type safe (generic) collection if it is the same as defined
  129. * by the JavaBean property
  130. *
  131. * @param property - JavaBean property
  132. * @param node - representation of the property
  133. * @param object - instance represented by the node
  134. */
  135. @SuppressWarnings("unchecked")
  136. protected void checkGlobalTag(Property property, Node node, Object object) {
  137. // Skip primitive arrays.
  138. if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) {
  139. return;
  140. }
  141. Class<?>[] arguments = property.getActualTypeArguments();
  142. if (arguments != null) {
  143. if (node.getNodeId() == NodeId.sequence) {
  144. // apply map tag where class is the same
  145. Class<? extends Object> t = arguments[0];
  146. SequenceNode snode = (SequenceNode) node;
  147. Iterable<Object> memberList = Collections.emptyList();
  148. if (object.getClass().isArray()) {
  149. memberList = Arrays.asList((Object[]) object);
  150. } else if (object instanceof Iterable<?>) {
  151. // list
  152. memberList = (Iterable<Object>) object;
  153. }
  154. Iterator<Object> iter = memberList.iterator();
  155. if (iter.hasNext()) {
  156. for (Node childNode : snode.getValue()) {
  157. Object member = iter.next();
  158. if (member != null) {
  159. if (t.equals(member.getClass())) {
  160. if (childNode.getNodeId() == NodeId.mapping) {
  161. childNode.setTag(Tag.MAP);
  162. }
  163. }
  164. }
  165. }
  166. }
  167. } else if (object instanceof Set) {
  168. Class<?> t = arguments[0];
  169. MappingNode mnode = (MappingNode) node;
  170. Iterator<NodeTuple> iter = mnode.getValue().iterator();
  171. Set<?> set = (Set<?>) object;
  172. for (Object member : set) {
  173. NodeTuple tuple = iter.next();
  174. Node keyNode = tuple.getKeyNode();
  175. if (t.equals(member.getClass())) {
  176. if (keyNode.getNodeId() == NodeId.mapping) {
  177. keyNode.setTag(Tag.MAP);
  178. }
  179. }
  180. }
  181. } else if (object instanceof Map) { // NodeId.mapping ends-up here
  182. Class<?> keyType = arguments[0];
  183. Class<?> valueType = arguments[1];
  184. MappingNode mnode = (MappingNode) node;
  185. for (NodeTuple tuple : mnode.getValue()) {
  186. resetTag(keyType, tuple.getKeyNode());
  187. resetTag(valueType, tuple.getValueNode());
  188. }
  189. } else {
  190. // the type for collection entries cannot be
  191. // detected
  192. }
  193. }
  194. }
  195. private void resetTag(Class<? extends Object> type, Node node) {
  196. Tag tag = node.getTag();
  197. if (tag.matches(type)) {
  198. if (Enum.class.isAssignableFrom(type)) {
  199. node.setTag(Tag.STR);
  200. } else {
  201. node.setTag(Tag.MAP);
  202. }
  203. }
  204. }
  205. /**
  206. * Get JavaBean properties to be serialised. The order is respected. This method may be overridden
  207. * to provide custom property selection or order.
  208. *
  209. * @param type - JavaBean to inspect the properties
  210. * @return properties to serialise
  211. */
  212. protected Set<Property> getProperties(Class<? extends Object> type) {
  213. if (typeDefinitions.containsKey(type)) {
  214. return typeDefinitions.get(type).getProperties();
  215. }
  216. return getPropertyUtils().getProperties(type);
  217. }
  218. }

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

  1. @Getter
  2. @Setter
  3. @ToString
  4. @Accessors(chain = true)
  5. @TableName("tb_user")
  6. @Schema(description = "用户")
  7. public class User implements Serializable {
  8. @Serial
  9. private static final long serialVersionUID = 1129496589110730436L;
  10. @TableId(value = "id", type = IdType.ASSIGN_ID)
  11. @Schema(description = "主键ID")
  12. private Long id;
  13. @TableField("create_time")
  14. @Schema(description = "创建时间")
  15. private Long createTime;
  16. @TableField("name")
  17. @Schema(description = "姓名")
  18. private String name;
  19. }

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

  1. DROP TABLE IF EXISTS tb_user;
  2. CREATE TABLE `tb_user`
  3. (
  4. `id` BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
  5. `create_time` BIGINT DEFAULT NULL COMMENT '创建时间',
  6. `name` VARCHAR(32) DEFAULT NULL COMMENT '姓名'
  7. );
  8. DROP TABLE IF EXISTS tb_user0;
  9. CREATE TABLE `tb_user0`
  10. (
  11. `id` BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
  12. `create_time` BIGINT DEFAULT NULL COMMENT '创建时间',
  13. `name` VARCHAR(32) DEFAULT NULL COMMENT '姓名'
  14. );
  15. DROP TABLE IF EXISTS tb_user1;
  16. CREATE TABLE `tb_user1`
  17. (
  18. `id` BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
  19. `create_time` BIGINT DEFAULT NULL COMMENT '创建时间',
  20. `name` VARCHAR(32) DEFAULT NULL COMMENT '姓名'
  21. )

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

  1. # 数据源配置
  2. dataSources:
  3. dsA:
  4. dataSourceClassName: com.zaxxer.hikari.HikariDataSource
  5. driverClassName: org.h2.Driver
  6. url: jdbc:h2:mem:db_sharding
  7. username: sa # 用户名,用于控制台登录
  8. password: 123456 # 密码,用于控制台登录
  9. dsB0:
  10. dataSourceClassName: com.zaxxer.hikari.HikariDataSource
  11. driverClassName: org.h2.Driver
  12. url: jdbc:h2:mem:db_sharding
  13. username: sa # 用户名,用于控制台登录
  14. password: 123456 # 密码,用于控制台登录
  15. dsB1:
  16. dataSourceClassName: com.zaxxer.hikari.HikariDataSource
  17. driverClassName: org.h2.Driver
  18. url: jdbc:h2:mem:db_sharding
  19. username: sa # 用户名,用于控制台登录
  20. password: 123456 # 密码,用于控制台登录
  21. # 规则配置
  22. rules:
  23. - !SINGLE
  24. tables:
  25. - "*.*"
  26. - !SHARDING
  27. tables:
  28. # 逻辑表名
  29. tb_user:
  30. actualDataNodes: dsB0.tb_user${0..1}
  31. # 分库策略
  32. tableStrategy:
  33. standard:
  34. # 分片列名称
  35. shardingColumn: id
  36. # 分片算法名称
  37. shardingAlgorithmName: inline_id
  38. # 分片算法配置
  39. shardingAlgorithms:
  40. # 标准分片算法-行表达式分片算法
  41. inline_id:
  42. # 基于行表达式的分片算法
  43. type: INLINE
  44. props:
  45. algorithm-expression: tb_user${id % 2}
  46. # 属性配置
  47. props:
  48. sql-show: true

最后,启动项目

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

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

整合DEMO仓库地址


本文转载自: https://blog.csdn.net/qq_37220802/article/details/137262023
版权归原作者 ゞ註﹎錠oo 所有, 如有侵权,请联系我们删除。

“spring-boot 整合 shardingsphere-jdbc、mybatis-plus 数据分片(文末有彩蛋)”的评论:

还没有评论