Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Database and Table Sharding with Spring Boot and ShardingSphere

Tech 1

Database and Table Initialization

Create two separate databases for horizontal partitioning:

sql CREATE SCHEMA partition_ds_0 DEFAULT CHARACTER SET utf8 ; CREATE SCHEMA partition_ds_1 DEFAULT CHARACTER SET utf8 ;

Within each database, generate three identical order tables:

sql CREATE TABLE purchase_record_0 ( id bigint(20) NOT NULL, customer_id bigint(20) not null, merchant_id bigint(20) not null, product_name varchar(64) not NULL, amount decimal(10,2) DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE purchase_record_1 ( id bigint(20) NOT NULL, customer_id bigint(20) not null, merchant_id bigint(20) not null, product_name varchar(64) not NULL, amount decimal(10,2) DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE purchase_record_2 ( id bigint(20) NOT NULL, customer_id bigint(20) not null, merchant_id bigint(20) not null, product_name varchar(64) not NULL, amount decimal(10,2) DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Maven Dependencies

xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> 3.1.5 <relativePath/> <groupId>com.tech.demo</groupId> <artifactId>sharding-sphere-demo</artifactId> 0.0.1-SNAPSHOT <java.version>17</java.version> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> true <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> test <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> 8.0.33 <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> 3.5.7 <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> 5.2.1 <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> 5.8.25 <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId>

Entity Definitions

java import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Builder; import lombok.Data; import java.math.BigDecimal;

@Data @Builder @TableName("customer") public class Customer { @TableId(type = IdType.ASSIGN_ID) private Long id; private String fullName; private Boolean gender; private Integer age; }

@Data @Builder @TableName("merchant") public class Merchant { @TableId(type = IdType.ASSIGN_ID) private Long id; private String storeName; private Boolean gender; private Integer age; }

@Data @Builder @TableName("purchase_record") public class PurchaseRecord { @TableId(type = IdType.ASSIGN_ID) private Long id; private Long customerId; private Long merchantId; private String productName; private BigDecimal amount; }

Mapper Interfaces

java import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper;

@Mapper public interface CustomerMapper extends BaseMapper<Customer> {}

@Mapper public interface MerchantMapper extends BaseMapper<Merchant> {}

@Mapper public interface PurchaseRecordMapper extends BaseMapper<PurchaseRecord> {}

REST Controller

java import com.tech.demo.mapper.CustomerMapper; import com.tech.demo.mapper.MerchantMapper; import com.tech.demo.mapper.PurchaseRecordMapper; import com.tech.demo.entity.PurchaseRecord; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*;

import java.util.HashMap; import java.util.Map;

@RestController @RequiredArgsConstructor public class PurchaseController {

private final CustomerMapper customerMapper;
private final MerchantMapper merchantMapper;
private final PurchaseRecordMapper purchaseMapper;

@GetMapping("/purchases/{id}")
public Map<String, Object> fetchDetails(@PathVariable Long id) {
    Map<String, Object> resultMap = new HashMap<>();
    PurchaseRecord record = purchaseMapper.selectById(id);
    if (record != null) {
        resultMap.put("record", record);
        resultMap.put("customer", customerMapper.selectById(record.getCustomerId()));
        resultMap.put("merchant", merchantMapper.selectById(record.getMerchantId()));
    }
    return resultMap;
}

}

Distributed ID Generator

java import cn.hutool.core.lang.Snowflake; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.RandomUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component;

import java.net.Inet4Address; import java.net.InetAddress; import java.net.UnknownHostException;

@Slf4j @Component public class DistributedIdGenerator implements ApplicationRunner {

private long nodeId;
private long clusterId;
private Snowflake snowflake;

@Override
public void run(ApplicationArguments args) {
    initializeNodeId();
    initializeClusterId();
    createSnowflake(nodeId, clusterId);
}

private void initializeNodeId() {
    try {
        String hostAddress = Inet4Address.getLocalHost().getHostAddress();
        int[] codePoints = StringUtils.toCodePoints(hostAddress);
        int sum = 0;
        for (int point : codePoints) {
            sum += point;
        }
        nodeId = sum % 32;
    } catch (UnknownHostException e) {
        log.error("Failed to resolve IP for Node ID", e);
        nodeId = RandomUtil.randomLong(0, 31);
    }
}

private void initializeClusterId() {
    String hostName = resolveHostName();
    int[] codePoints = StringUtils.toCodePoints(hostName);
    int sum = 0;
    for (int point : codePoints) {
        sum += point;
    }
    clusterId = sum % 32;
}

private String resolveHostName() {
    String osName = System.getProperty("os.name");
    if (!StringUtils.startsWithIgnoreCase(osName, "mac")) {
        return SystemUtils.getHostName();
    }
    try {
        return InetAddress.getLocalHost().getHostName().toUpperCase();
    } catch (UnknownHostException e) {
        log.error("Failed to resolve Host Name", e);
        return "FALLBACK_HOST";
    }
}

private Snowflake createSnowflake(long workerId, long datacenterId) {
    try {
        this.snowflake = IdUtil.createSnowflake(workerId, datacenterId);
        if (this.snowflake == null) {
            throw new IllegalStateException("Snowflake instance initialization failed.");
        }
        return this.snowflake;
    } catch (Exception e) {
        log.error("Snowflake creation error: {}", e.getMessage());
        throw new RuntimeException("ID Generator initialization failure.", e);
    }
}

public synchronized long nextId() {
    return this.snowflake.nextId();
}

public synchronized long nextId(long workerId, long datacenterId) {
    return createSnowflake(workerId, datacenterId).nextId();
}

}

Unit Tests

java import cn.hutool.core.util.RandomUtil; import cn.hutool..JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.tech.demo.entity.PurchaseRecord; import com.tech.demo.mapper.PurchaseRecordMapper; import com.tech.demo.provider.DistributedIdGenerator; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired;

import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List;

@Slf4j public class ShardingIntegrationTest extends BaseTestContainer {

@Autowired
private DistributedIdGenerator idGenerator;

@Autowired
private PurchaseRecordMapper purchaseMapper;

@Test
public void insertBatchRecords() {
    List<PurchaseRecord> records = new ArrayList<>();
    for (int i = 1; i <= 10; i++) {
        PurchaseRecord record = PurchaseRecord.builder()
                .customerId(idGenerator.nextId())
                .merchantId(idGenerator.nextId())
                .productName("Product_" + RandomUtil.randomInt(6))
                .amount(RandomUtil.randomBigDecimal().setScale(2, RoundingMode.HALF_UP))
                .build();
        records.add(record);
    }
    purchaseMapper.insert(records);
}

@Test
public void fetchSingleRecord() {
    PurchaseRecord record = purchaseMapper.selectById(5L);
    Assert.assertNotNull(record);
    log.info("Query Result: {}", JSONUtil.toJsonStr(record));
}

@Test
public void modifyRecord() {
    PurchaseRecord record = purchaseMapper.selectById(3L);
    Assert.assertNotNull(record);
    record.setCustomerId(1L);
    record.setMerchantId(3L);
    int affectedRows = purchaseMapper.updateById(record);
    log.info("Rows affected: {}", affectedRows);
}

@Test
public void fetchPaginatedRecords() {
    Page<PurchaseRecord> pageConfig = new Page<>(1, 2);
    LambdaQueryWrapper<PurchaseRecord> wrapper = new LambdaQueryWrapper<>();
    Page<PurchaseRecord> resultPage = purchaseMapper.selectPage(pageConfig, wrapper);
    log.info("Pagination Result: {}", JSONUtil.toJsonStr(resultPage));
}

}

Application Configuration

yaml server: port: 8095

spring: application: name: sharding-sphere-demo shardingsphere: mode: type: Standalone repository: type: JDBC database: name: ds_0 datasource: names: ds_0,ds_1 ds_0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/partition_ds_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: root ds_1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/partition_ds_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: root rules: sharding: sharding-algorithms: ds-inline: type: INLINE props: algorithm-expression: ds_${id % 2} tbl-inline: type: INLINE props: algorithm-expression: purchase_record_${id % 3} tables: purchase_record: actual-data-nodes: ds_${0..1}.purchase_record_${0..2} database-strategy: standard: sharding-column: id sharding-algorithm-name: ds-inline table-strategy: standard: sharding-column: id sharding-algorithm-name: tbl-inline props: sql-show: true

mybatis-plus: type-aliases-package: com.tech.demo.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl mapper-locations: - classpath:/mapper/*.xml

Compatibility Fix: SnakeYAML NoSuchMethodError

When utilizing shardingsphere-jdbc-core-spring-boot-starter version 5.2.1 with Spring Boot 3.x, a compatibility issue arises due to SnakeYAML version conflicts. ShardingSphere relies on SnakeYAML 1.33, while Spring Boot 3.x bundles SnakeYAML 2.2. Version 2.2 removed the no-argument constructors for Representer and SafeRepresenter, causing a NoSuchMethodError.

To resolve this without downgrading Spring Boot (which would introduce security vulnerabilities from older SnakeYAML versions), create local class shadow overrides within your project under the org.yaml.snakeyaml.representer package. This restores the required constructors.

org.yaml.snakeyaml.representer.Representer

java package org.yaml.snakeyaml.representer;

import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.TypeDescription; import org.yaml.snakeyaml.introspector.Property; import org.yaml.snakeyaml.introspector.PropertyUtils; import org.yaml.snakeyaml.nodes.*;

import java.util.*;

public class Representer extends SafeRepresenter { protected Map<Class<?>, TypeDescription> typeDefinitions = Collections.emptyMap();

public Representer() {
    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 == this.typeDefinitions) {
        this.typeDefinitions = new HashMap<>();
    }
    if (td.getTag() != null) {
        this.addClassTag(td.getType(), td.getTag());
    }
    td.setPropertyUtils(this.getPropertyUtils());
    return this.typeDefinitions.put(td.getType(), td);
}

public void setPropertyUtils(PropertyUtils propertyUtils) {
    super.setPropertyUtils(propertyUtils);
    for (TypeDescription td : this.typeDefinitions.values()) {
        td.setPropertyUtils(propertyUtils);
    }
}

protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
    List<NodeTuple> value = new ArrayList<>(properties.size());
    Tag customTag = this.classTags.get(javaBean.getClass());
    Tag tag = customTag != null ? customTag : new Tag(javaBean.getClass());
    MappingNode node = new MappingNode(tag, value, DumperOptions.FlowStyle.AUTO);
    this.representedObjects.put(javaBean, node);
    DumperOptions.FlowStyle bestStyle = DumperOptions.FlowStyle.FLOW;
    for (Property property : properties) {
        Object memberValue = property.get(javaBean);
        Tag customPropertyTag = memberValue == null ? null : this.classTags.get(memberValue.getClass());
        NodeTuple tuple = this.representJavaBeanProperty(javaBean, property, memberValue, customPropertyTag);
        if (tuple == null) continue;
        if (!((ScalarNode) tuple.getKeyNode()).isPlain()) bestStyle = DumperOptions.FlowStyle.BLOCK;
        Node nodeValue = tuple.getValueNode();
        if (!(nodeValue instanceof ScalarNode) || !((ScalarNode) nodeValue).isPlain()) bestStyle = DumperOptions.FlowStyle.BLOCK;
        value.add(tuple);
    }
    if (this.defaultFlowStyle != DumperOptions.FlowStyle.AUTO) {
        node.setFlowStyle(this.defaultFlowStyle);
    } else {
        node.setFlowStyle(bestStyle);
    }
    return node;
}

protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
    ScalarNode nodeKey = (ScalarNode) this.representData(property.getName());
    boolean hasAlias = this.representedObjects.containsKey(propertyValue);
    Node nodeValue = this.representData(propertyValue);
    if (propertyValue != null && !hasAlias) {
        NodeId nodeId = nodeValue.getNodeId();
        if (customTag == null) {
            if (nodeId == NodeId.scalar) {
                if (property.getType() != Enum.class && propertyValue instanceof Enum) {
                    nodeValue.setTag(Tag.STR);
                }
            } else {
                if (nodeId == NodeId.mapping && property.getType() == propertyValue.getClass() && !(propertyValue instanceof Map) && !nodeValue.getTag().equals(Tag.SET)) {
                    nodeValue.setTag(Tag.MAP);
                }
                this.checkGlobalTag(property, nodeValue, propertyValue);
            }
        }
    }
    return new NodeTuple(nodeKey, nodeValue);
}

protected void checkGlobalTag(Property property, Node node, Object object) {
    if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) return;
    Class<?>[] arguments = property.getActualTypeArguments();
    if (arguments != null) {
        if (node.getNodeId() == NodeId.sequence) {
            Class<?> t = arguments[0];
            SequenceNode snode = (SequenceNode) node;
            Iterable<Object> memberList = object.getClass().isArray() ? Arrays.asList((Object[]) object) : (Iterable<Object>) object;
            Iterator<Object> iter = memberList.iterator();
            if (iter.hasNext()) {
                for (Node childNode : snode.getValue()) {
                    Object member = iter.next();
                    if (member != null && t.equals(member.getClass()) && childNode.getNodeId() == NodeId.mapping) {
                        childNode.setTag(Tag.MAP);
                    }
                }
            }
        } else if (object instanceof Set) {
            Class<?> t = arguments[0];
            MappingNode mnode = (MappingNode) node;
            Iterator<NodeTuple> ite = mnode.getValue().iterator();
            Set<?> set = (Set<?>) object;
            for (Object member : set) {
                NodeTuple tuple = ite.next();
                Node keyNode = tuple.getKeyNode();
                if (t.equals(member.getClass()) && keyNode.getNodeId() == NodeId.mapping) keyNode.setTag(Tag.MAP);
            }
        } else if (object instanceof Map) {
            Class<?> t = arguments[0];
            Class<?> valueType = arguments[1];
            MappingNode mnode = (MappingNode) node;
            for (NodeTuple tuple : mnode.getValue()) {
                this.resetTag(t, tuple.getKeyNode());
                this.resetTag(valueType, tuple.getValueNode());
            }
        }
    }
}

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);
    }
}

protected Set<Property> getProperties(Class<?> type) {
    return this.typeDefinitions.containsKey(type) ? this.typeDefinitions.get(type).getProperties() : this.getPropertyUtils().getProperties(type);
}

protected class RepresentJavaBean implements Represent {
    public Node representData(Object data) {
        return Representer.this.representJavaBean(Representer.this.getProperties(data.getClass()), data);
    }
}

}

org.yaml.snakeyaml.representer.SafeRepresenter

java package org.yaml.snakeyaml.representer;

import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.Tag;

public class SafeRepresenter extends BaseRepresenter { protected Map<Class<?>, Tag> classTags; protected DumperOptions.NonPrintableStyle nonPrintableStyle;

public SafeRepresenter() {
    this(new DumperOptions());
}

public SafeRepresenter(DumperOptions options) {
    if (options == null) throw new NullPointerException("DumperOptions must be provided.");
    this.nullRepresenter = new RepresentNull();
    this.representers.put(String.class, new RepresentString());
    this.representers.put(Boolean.class, new RepresentBoolean());
    this.representers.put(Character.class, new RepresentString());
    this.classTags = new HashMap<>();
    this.nonPrintableStyle = options.getNonPrintableStyle();
    this.setDefaultScalarStyle(options.getDefaultScalarStyle());
    this.setDefaultFlowStyle(options.getDefaultFlowStyle());
}

protected Tag getTag(Class<?> clazz, Tag defaultTag) {
    return this.classTags.containsKey(clazz) ? this.classTags.get(clazz) : defaultTag;
}

public Tag addClassTag(Class<?> clazz, Tag tag) {
    if (tag == null) throw new NullPointerException("Tag must be provided.");
    return this.classTags.put(clazz, tag);
}

protected class RepresentNull implements Represent {
    public Node representData(Object data) {
        return SafeRepresenter.this.representScalar(Tag.NULL, "null");
    }
}

protected class RepresentString implements Represent {
    public Node representData(Object data) {
        return SafeRepresenter.this.representScalar(Tag.STR, data.toString());
    }
}

protected class RepresentBoolean implements Represent {
    public Node representData(Object data) {
        return SafeRepresenter.this.representScalar(Tag.BOOL, Boolean.TRUE.equals(data) ? "true" : "false");
    }
}

}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.