ShardingSphere-JDBC 开发手册

1.概述

1.1.什么是 ShardingSphere-JDBC?

ShardingSphere-JDBC是轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。它使⽤客⼾端直连数据库,以 jar 包形式 提供服务,⽆需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

本文基于 ShardingSphere 5.1.0 版本编写测试,不同版本之间有些许差异,可参考官方文档进行调整。

官方文档地址:https://shardingsphere.apache.org/document/current/cn/overview/

1.2.核心特性
特性 描述 适用场景
数据分片 水平拆分数据库/表 大数据量、高并发
读写分离 主从架构自动路由 读多写少
数据加密 透明化数据加密 敏感数据保护
分布式事务 XA/BASE事务支持 跨库事务一致性
影子库压测 全链路压测隔离 生产压测
弹性伸缩 动态扩缩容 业务快速增长

2.环境搭建

2.1.系统要求
xml 复制代码
<!-- 环境依赖矩阵 -->
+----------------+-----------------+-------------------+
| 组件           | 最低版本         | 推荐版本           |
+----------------+-----------------+-------------------+
| JDK            | 1.8             | 11/17 (LTS)       |
| Maven          | 3.5             | 3.8+              |
| HighGoDB       | 4.5             | 4.5+              |
| Spring Boot    | 2.0             | 2.7+/3.0+         |
+----------------+-----------------+-------------------+
2.2.基础环境搭建
2.2.1.安装环境
  1. jdk: 要求jdk必须是1.8版本及以上
  2. 搭建瀚高数据库
2.2.2.搭建springboot程序
  1. 组件说明:
  • SpringBoot 2.7.14
  • MyBatisPlus 3.5.3.1
  • ShardingSphere-JDBC 5.1.0
  • Hikari
  • hgdb v4.5
  1. 引入依赖
xml 复制代码
<properties>
  <java.version>17</java.version>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <shardingsphere.version>5.1.1</shardingsphere.version>
  <mybatisplus.version>3.3.1</mybatisplus.version>
  <hgdb.version>42.5.0</hgdb.version>
</properties>

<dependencies>
  <!-- Spring Boot -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!-- MyBatis Plus -->
  <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis-plus.version}</version>
  </dependency>

  <!-- ShardingSphere -->
  <dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>${shardingsphere.version}</version>
  </dependency>

  <!-- HighGoDB -->
  <dependency>
    <groupId>com.highgo</groupId>
    <artifactId>hgdb-pgjdbc</artifactId>
    <version>${hgdb.version}</version>
  </dependency>

  <!-- HikariCP (Spring Boot默认使用) -->
  <dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
  </dependency>

  <!-- Lombok -->
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
  </dependency>

  <!-- 加密工具 -->
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>

</dependencies>

3.分表不分库

3.1.数据库准备
sql 复制代码
-- 创建数据库
CREATE DATABASE test_db;

-- 切换到test_db数据库

-- 创建分表 t_order_0 和 t_order_1
CREATE TABLE IF NOT EXISTS t_order_0 (
  order_id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(50) NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  status INT DEFAULT 0,
  create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  deleted INT DEFAULT 0
);

CREATE INDEX ON t_order_0(user_id);
CREATE INDEX ON t_order_0(create_time);

CREATE TABLE IF NOT EXISTS t_order_1 (
  order_id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(50) NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  status INT DEFAULT 0,
  create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  deleted INT DEFAULT 0 
);

CREATE INDEX ON t_order_1(user_id);
CREATE INDEX ON t_order_1(create_time);
3.2.主配置文件 (application.yml)
yaml 复制代码
spring:
  # ShardingSphere 配置
  shardingsphere:
    # 数据源配置
    datasource:
      names: ds
      ds:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.100.108:5866/test_db
        username: sysdba
        password: Qwer@12345
        hikari:
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          maximum-pool-size: 20
          minimum-idle: 5

    # 分片规则配置
    rules:
      sharding:
        # 分表规则
        tables:
          t_order:
            # 实际数据节点,支持groovy表达式
            actual-data-nodes: ds.t_order_$->{0..1}
            # 分表策略
            table-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: order-table-inline
            # 主键生成策略
            key-generate-strategy:
              column: order_id
              key-generator-name: snowflake

        # 分片算法
        sharding-algorithms:
          order-table-inline:
            type: INLINE
            props:
              algorithm-expression: t_order_$->{user_id % 2}

        # 主键生成器
        key-generators:
          snowflake:
            type: SNOWFLAKE

    # 属性配置
    props:
      sql-show: true  # 显示SQL

# MyBatis Plus 配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

# 应用配置
server:
  port: 8080
3.3.实体类
java 复制代码
package com.yoyo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单实体类
 */
@Data
@TableName("t_order")  // 逻辑表名
public class Order {

    /**
     * 订单ID,使用Snowflake算法生成
     */
    @TableId(type = IdType.ASSIGN_ID)
    private Long orderId;

    /**
     * 用户ID,作为分片键
     */
    private Long userId;

    /**
     * 订单号
     */
    private String orderNo;

    /**
     * 订单金额
     */
    private BigDecimal amount;

    /**
     * 订单状态:0-待支付,1-已支付,2-已完成,3-已取消
     */
    private Integer status;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;

    /**
     * 逻辑删除标识
     */
    private Integer deleted;
}
3.4.Mapper接口
java 复制代码
package com.yoyo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yoyo.entity.Order;
import org.apache.ibatis.annotations.Mapper;

/**
 * 订单Mapper接口
 */
@Mapper
public interface OrderMapper extends BaseMapper<Order> {

}
3.5.Service接口
java 复制代码
package com.yoyo.service;

import com.yoyo.entity.Order;

import java.util.List;

public interface OrderService {
    /**
     * 创建订单
     */
    Order createOrder(Order order);

    /**
     * 批量创建订单
     */
    List<Order> batchCreateOrders(List<Order> orders);

    /**
     * 根据用户ID查询订单
     */
    List<Order> getOrdersByUserId(Long userId);

    /**
     * 测试跨表查询
     */
    List<Order> queryAllOrders();
}
3.6.Service实现
java 复制代码
package com.yoyo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yoyo.entity.Order;
import com.yoyo.mapper.OrderMapper;
import com.yoyo.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Order createOrder(Order order) {
        order.setCreateTime(LocalDateTime.now());
        order.setUpdateTime(LocalDateTime.now());
        order.setDeleted(0);

        this.save(order);

        log.info("创建订单成功,订单ID: {}, 用户ID: {}",
                order.getOrderId(), order.getUserId());

        return order;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<Order> batchCreateOrders(List<Order> orders) {
        for (Order order : orders) {
            order.setCreateTime(LocalDateTime.now());
            order.setUpdateTime(LocalDateTime.now());
            order.setDeleted(0);
        }

        this.saveBatch(orders);
        log.info("批量创建订单成功,共{}条", orders.size());

        return orders;
    }

    @Override
    public List<Order> getOrdersByUserId(Long userId) {
        QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id",userId);
        List<Order> orders = this.list(queryWrapper);
        log.info("根据用户ID: {} 查询到 {} 条订单", userId, orders.size());
        return orders;
    }

    @Override
    public List<Order> queryAllOrders() {
        // 注意:这会查询所有分片表
        List<Order> orders = this.list();
        log.info("跨表查询到 {} 条订单", orders.size());
        return orders;
    }
}
3.7.测试用例
java 复制代码
package com.yoyo;

import com.yoyo.entity.Order;
import com.yoyo.service.impl.OrderServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

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

@Slf4j
@SpringBootTest
class SpringbootShardingJdbcDemoApplicationTests {

    @Autowired
    private OrderServiceImpl orderService;

     @Test
    void testInsertShardingTable() {

        Order order1 = new Order();
        order1.setUserId(1L);  // 会插入到 t_order_1 (1 % 2 = 1)
        order1.setOrderNo("TEST001");
        order1.setAmount(BigDecimal.valueOf(100.50));
        order1.setStatus(1);

        Order order2 = new Order();
        order2.setUserId(2L);  // 会插入到 t_order_0 (2 % 2 = 0)
        order2.setOrderNo("TEST002");
        order2.setAmount(BigDecimal.valueOf(200.00));
        order2.setStatus(2);

        orderService.createOrder(order1);
        orderService.createOrder(order2);

        log.info("测试数据插入完成");
    }
    @Test
    void testBatchInsertShardingTable() {
        List<Order> orders = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            Order order = new Order();
            order.setUserId((long) i);  // 根据用户ID分片
            order.setOrderNo("BATCH" + i);
            order.setAmount(BigDecimal.valueOf(i * 50));
            order.setStatus(i % 4);
            orders.add(order);
        }

        orderService.batchCreateOrders(orders);
        log.info("批量插入完成,共{}条记录", orders.size());
    }
    @Test
    void testQueryByShardingKeyShardingTable() {
        Long userId = 3L;
        List<Order> orders = orderService.getOrdersByUserId(userId);
        log.info("用户ID: {} 的订单数量: {}", userId, orders.size());

        for (Order order : orders) {
            log.info("订单: {}, 金额: {}", order.getOrderNo(), order.getAmount());
        }
    }
    @Test
    void testCrossTableQueryShardingTable() {
        List<Order> allOrders = orderService.queryAllOrders();
        log.info("所有订单数量: {}", allOrders.size());

        // 统计各分片表的订单数量
        long count0 = allOrders.stream()
                .filter(order -> order.getUserId() % 2 == 0)
                .count();
        long count1 = allOrders.stream()
                .filter(order -> order.getUserId() % 2 == 1)
                .count();

        log.info("t_order_0 表订单数: {}", count0);
        log.info("t_order_1 表订单数: {}", count1);
    }
}

4.分库不分表

4.1.数据准备
shell 复制代码
-- 创建数据库0
CREATE DATABASE test_db_0;

-- 创建数据库1
CREATE DATABASE test_db_1;

-- 切换到 test_db_0

-- 创建订单表
CREATE TABLE IF NOT EXISTS t_order (
    order_id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    order_no VARCHAR(50) NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    status INT DEFAULT 0,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    deleted INT DEFAULT 0
    );

CREATE INDEX ON t_order(user_id);
CREATE INDEX ON t_order(create_time);

-- 切换到 test_db_1

-- 创建订单表
CREATE TABLE IF NOT EXISTS t_order (
    order_id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    order_no VARCHAR(50) NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    status INT DEFAULT 0,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    deleted INT DEFAULT 0
    );

CREATE INDEX ON t_order(user_id);
CREATE INDEX ON t_order(create_time);
4.2.主配置文件 (application.yml)
yaml 复制代码
spring:
  shardingsphere:
    # 数据源配置 - 两个数据库实例
    datasource:
      names: ds0,ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.100.108:5866/test_db_0
        username: sysdba
        password: Qwer@12345
        hikari:
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          maximum-pool-size: 20
          minimum-idle: 5
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.100.108:5866/test_db_1
        username: sysdba
        password: Qwer@12345
        hikari:
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          maximum-pool-size: 20
          minimum-idle: 5

    # 分片规则配置
    rules:
      sharding:
        # 默认数据库分片策略
        default-database-strategy:
          standard:
            sharding-column: user_id
            sharding-algorithm-name: database-inline

        # 表配置(不分表)
        tables:
          t_order:
            # 实际数据节点,分布在两个数据库
            actual-data-nodes: ds$->{0..1}.t_order
            # 主键生成策略
            key-generate-strategy:
              column: order_id
              key-generator-name: snowflake

        # 分片算法
        sharding-algorithms:
          database-inline:
            type: INLINE
            props:
              algorithm-expression: ds$->{user_id % 2}

        # 主键生成器
        key-generators:
          snowflake:
            type: SNOWFLAKE

    # 属性配置
    props:
      sql-show: true  # 显示SQL
      sql-simple: true # 简化SQL日志

# MyBatis Plus 配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

# 应用配置
server:
  port: 8080
4.3.实体类

同上。

4.4.Mapper接口

同上。

4.5.Service接口

同上。

4.6.Service实现

同上。

4.7.测试用例
java 复制代码
@Test
void testInsertShardingDatabase() {

    Order order1 = new Order();
    order1.setUserId(1L);  // 会插入到 test_db_1.t_order (1 % 2 = 1)
    order1.setOrderNo("TEST001");
    order1.setAmount(BigDecimal.valueOf(100.50));
    order1.setStatus(1);

    Order order2 = new Order();
    order2.setUserId(2L);  // 会插入到 test_db_0.t_order (2 % 2 = 0)
    order2.setOrderNo("TEST002");
    order2.setAmount(BigDecimal.valueOf(200.00));
    order2.setStatus(2);

    orderService.createOrder(order1);
    orderService.createOrder(order2);

    log.info("测试数据插入完成");
}
@Test
void testBatchInsertShardingDatabase() {
    List<Order> orders = new ArrayList<>();
    for (int i = 0; i < 20; i++) {
        Order order = new Order();
        order.setUserId((long) i);  // 根据用户ID分片
        order.setOrderNo("BATCH" + i);
        order.setAmount(BigDecimal.valueOf(i * 50));
        order.setStatus(i % 4);
        orders.add(order);
    }

    orderService.batchCreateOrders(orders);
    log.info("批量插入完成,共{}条记录", orders.size());
}
@Test
void testQueryByShardingKeyShardingDatabase() {
    Long userId = 3L;
    List<Order> orders = orderService.getOrdersByUserId(userId);
    log.info("用户ID: {} 的订单数量: {}", userId, orders.size());

    for (Order order : orders) {
        log.info("订单: {}, 金额: {}", order.getOrderNo(), order.getAmount());
    }
}
@Test
void testCrossTableQueryShardingDatabase() {
    List<Order> allOrders = orderService.queryAllOrders();
    log.info("所有订单数量: {}", allOrders.size());

    // 统计各分片表的订单数量
    long count0 = allOrders.stream()
            .filter(order -> order.getUserId() % 2 == 0)
            .count();
    long count1 = allOrders.stream()
            .filter(order -> order.getUserId() % 2 == 1)
            .count();

    log.info("test_db_0.t_order 表订单数: {}", count0);
    log.info("test_db_1.t_order 表订单数: {}", count1);
}

5.分库分表

5.1.数据准备
sql 复制代码
-- 创建数据库0
CREATE DATABASE test_db_0;

-- 创建数据库1
CREATE DATABASE test_db_1;

-- 切换到 test_db_0

-- 创建订单表t_order_0..1
CREATE TABLE IF NOT EXISTS t_order_0 (
  order_id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(50) NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  status INT DEFAULT 0,
  create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  deleted INT DEFAULT 0
);

CREATE INDEX ON t_order_0(user_id);
CREATE INDEX ON t_order_0(create_time);

CREATE TABLE IF NOT EXISTS t_order_1 (
  order_id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(50) NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  status INT DEFAULT 0,
  create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  deleted INT DEFAULT 0
);

CREATE INDEX ON t_order_1(user_id);
CREATE INDEX ON t_order_1(create_time);

-- 切换到 test_db_1

-- 创建订单表t_order0..1
CREATE TABLE IF NOT EXISTS t_order_0 (
  order_id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(50) NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  status INT DEFAULT 0,
  create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  deleted INT DEFAULT 0
);

CREATE INDEX ON t_order_0(user_id);
CREATE INDEX ON t_order_0(create_time);

CREATE TABLE IF NOT EXISTS t_order_1 (
  order_id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(50) NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  status INT DEFAULT 0,
  create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  deleted INT DEFAULT 0
);

CREATE INDEX ON t_order_1(user_id);
CREATE INDEX ON t_order_1(create_time);
5.2.主配置文件 (application.yml)
yaml 复制代码
spring:
  shardingsphere:
    # 数据源配置 - 两个数据库实例
    datasource:
      names: ds0,ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.100.108:5866/test_db_0
        username: sysdba
        password: Qwer@12345
        hikari:
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          maximum-pool-size: 20
          minimum-idle: 5
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.100.108:5866/test_db_1
        username: sysdba
        password: Qwer@12345
        hikari:
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          maximum-pool-size: 20
          minimum-idle: 5

    # 分片规则配置
    rules:
      sharding:
        # 默认数据库分片策略
        default-database-strategy:
          standard:
            sharding-column: user_id
            sharding-algorithm-name: database-inline

        # 表配置(分库分表)
        tables:
          # 订单表 - 分库分表 (2库 × 2表 = 4个分片)
          t_order:
            # 实际数据节点,分布在两个数据库
            actual-data-nodes: ds$->{0..1}.t_order_$->{0..1}
            # 数据库分片策略:按 user_id 分库
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: database-inline
            # 表分片策略:按 order_id 分表
            table-strategy:
              standard:
                sharding-column: order_id
                sharding-algorithm-name: order-table-inline

        # 分片算法
        sharding-algorithms:
          # 数据库分片算法:user_id % 2
          database-inline:
            type: INLINE
            props:
              algorithm-expression: ds$->{user_id % 2}
          # 表分片算法:order_id % 2
          order-table-inline:
            type: INLINE
            props:
              algorithm-expression: t_order_$->{order_id % 2}

    # 属性配置
    props:
      sql-show: true  # 显示SQL
      sql-simple: true # 简化SQL日志

# MyBatis Plus 配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

# 应用配置
server:
  port: 8080
5.3.实体类

同上。

5.4.Mapper接口

同上。

5.5.Service接口

同上。

5.6.Service实现

同上。

5.7.测试用例
yaml 复制代码
@Test
void testInsertShardingDatabaseAndTable() {

    Order order1 = new Order();
    order1.setOrderId(1001L); // 1001 % 2 = 1 → t_order_1
    order1.setUserId(101L);  // 101 % 2 = 1 → ds1
    order1.setOrderNo("TEST001");
    order1.setAmount(BigDecimal.valueOf(100.50));
    order1.setStatus(1);

    Order order2 = new Order();
    order2.setOrderId(1002L); // 1002 % 2 = 0 → t_order_0
    order2.setUserId(101L);  // 101 % 2 = 1 → ds1
    order2.setOrderNo("TEST002");
    order2.setAmount(BigDecimal.valueOf(200.50));
    order2.setStatus(2);

    Order order3 = new Order();
    order3.setOrderId(1003L); // 1003 % 2 = 1 → t_order_1
    order3.setUserId(102L);  // 102 % 2 = 0 → ds0
    order3.setOrderNo("TEST003");
    order3.setAmount(BigDecimal.valueOf(200.50));
    order3.setStatus(3);

    Order order4 = new Order();
    order4.setOrderId(1004L); // 1004 % 2 = 0 → t_order_0
    order4.setUserId(102L);  // 102 % 2 = 0 → ds0
    order4.setOrderNo("TEST004");
    order4.setAmount(BigDecimal.valueOf(200.50));
    order4.setStatus(4);

    orderService.createOrder(order1);
    orderService.createOrder(order2);
    orderService.createOrder(order3);
    orderService.createOrder(order4);

    log.info("测试数据插入完成");
}

@Test
void testBatchInsertShardingDatabaseAndTable() {
    List<Order> orders = new ArrayList<>();
    Random random = new Random();
    for (int i = 0; i < 20; i++) {
        Order order = new Order();
        long orderId = 1000L + i;
        long userId = random.nextInt(100);
        order.setOrderId(orderId); // 根据订单ID分表
        order.setUserId(userId);  // 根据用户ID分库
        order.setOrderNo("BATCH" + i);
        order.setAmount(BigDecimal.valueOf(i * 50));
        order.setStatus(i % 4);
        orders.add(order);
    }

    orderService.batchCreateOrders(orders);
    log.info("批量插入完成,共{}条记录", orders.size());
}

@Test
void testCrossTableQueryShardingDatabaseAndTable() {
    List<Order> allOrders = orderService.queryAllOrders();
    log.info("所有订单数量: {}", allOrders.size());

    // 统计各分片表的订单数量
    long count0 = allOrders.stream()
            .filter(order -> order.getUserId() % 2 == 0 && order.getOrderId() % 2 == 0)
            .count();
    long count1 = allOrders.stream()
            .filter(order -> order.getUserId() % 2 == 0 && order.getOrderId() % 2 == 1)
            .count();
    long count2 = allOrders.stream()
            .filter(order -> order.getUserId() % 2 == 1 && order.getOrderId() % 2 == 0)
            .count();
    long count3 = allOrders.stream()
            .filter(order -> order.getUserId() % 2 == 1 && order.getOrderId() % 2 == 1)
            .count();

    log.info("test_db_0.t_order_0 表订单数: {}", count0);
    log.info("test_db_0.t_order_1 表订单数: {}", count1);
    log.info("test_db_1.t_order_0 表订单数: {}", count2);
    log.info("test_db_1.t_order_1 表订单数: {}", count3);
}

6.自定义分片策略

6.1.自定义分库策略
java 复制代码
package com.yoyo.sharding.algorithm;

import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;

import java.util.Collection;
import java.util.Properties;

/**
 * 自定义分库策略
 * 根据 user_id 进行分库
 */
public class CustomDatabaseShardingAlgorithm implements StandardShardingAlgorithm<Long> {

    private Properties props = new Properties();

    @Override
    public String doSharding(Collection<String> availableTargetNames,
                             PreciseShardingValue<Long> shardingValue) {
        // availableTargetNames: 可用的数据源名称,如 ds0, ds1
        // shardingValue: 分片键的值,如 user_id=123

        Long userId = shardingValue.getValue();
        String shardingColumn = shardingValue.getColumnName();

        // 自定义分库逻辑:根据user_id的奇偶分库
        // 实际业务中可以根据业务规则,如用户地区、用户类型等

        // 简单的奇偶分库
        long dbIndex = userId % 2;

        for (String each : availableTargetNames) {
            if (each.endsWith(String.valueOf(dbIndex))) {
                return each;
            }
        }

        throw new IllegalArgumentException("未找到对应的数据库分片: userId=" + userId);
    }

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames,
                                         RangeShardingValue<Long> shardingValue) {
        // 范围查询的分片逻辑
        // 这里简单返回所有数据源,实际业务需要根据范围优化
        return availableTargetNames;
    }

    @Override
    public Properties getProps() {
        return props;
    }

    @Override
    public void init() {

    }

    @Override
    public String getType() {
        return "CUSTOM_DB_SHARDING";
    }
}
6.2.自定义分表策略
java 复制代码
package com.yoyo.sharding.algorithm;

import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;

import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Properties;

/**
 * 自定义分表策略
 * 根据 order_id 进行分表
 */
public class CustomTableShardingAlgorithm implements StandardShardingAlgorithm<Long> {

    private Properties props = new Properties();

    @Override
    public String doSharding(Collection<String> availableTargetNames,
                             PreciseShardingValue<Long> shardingValue) {
        Long orderId = shardingValue.getValue();
        String logicTableName = shardingValue.getLogicTableName();

         //方案1: 简单的奇偶分表
         long tableIndex = orderId % 2;

        // 方案2: 基于时间戳的分表(如果order_id包含时间戳)
        // 例如:雪花算法生成的ID包含时间戳信息
        //LocalDateTime createTime = extractTimeFromOrderId(orderId);
        //int yearMonth = createTime.getYear() * 100 + createTime.getMonthValue();
        //long tableIndex = yearMonth % 2; // 按年月分表

        // 方案3: 哈希分表(更均匀)
        // long tableIndex = Math.abs(orderId.hashCode()) % availableTargetNames.size();

        // 方案4: 根据业务规则分表(如订单类型)
        // long tableIndex = getOrderType(orderId) % 2;

        // 查找对应的表
        for (String each : availableTargetNames) {
            if (each.endsWith("_" + tableIndex)) {
                return each;
            }
        }

        throw new IllegalArgumentException("未找到对应的表分片: orderId=" + orderId);
    }

    /**
     * 从订单ID中提取时间信息(如果使用雪花算法)
     */
    private LocalDateTime extractTimeFromOrderId(Long orderId) {
        // 雪花算法示例:41位时间戳 + 10位机器ID + 12位序列号
        // 这里假设order_id是雪花算法生成的
        if (orderId == null) {
            return LocalDateTime.now();
        }

        long timestamp = (orderId >> 22) + 1288834974657L; // Twitter雪花算法起始时间
        return LocalDateTime.ofEpochSecond(timestamp / 1000, 0, java.time.ZoneOffset.UTC);
    }

    /**
     * 获取订单类型(示例)
     */
    private int getOrderType(Long orderId) {
        // 假设订单ID的第3-4位表示订单类型
        return (int) ((orderId / 10000) % 100);
    }

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames,
                                         RangeShardingValue<Long> shardingValue) {
        // 范围查询的分表逻辑
        // 这里简单返回所有表,实际需要根据时间范围等优化
        return availableTargetNames;
    }

    @Override
    public Properties getProps() {
        return props;
    }

    @Override
    public void init() {

    }

    @Override
    public String getType() {
        return "CUSTOM_TABLE_SHARDING";
    }
}
6.3.复合分片策略
java 复制代码
package com.yoyo.sharding.algorithm;

import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingValue;

import java.util.*;

/**
 * 复合分片策略
 * 同时考虑 user_id 和 order_time 进行分片
 */
public class ComplexShardingAlgorithm implements ComplexKeysShardingAlgorithm<Comparable<?>> {

    private Properties props = new Properties();

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames,
                                         ComplexKeysShardingValue<Comparable<?>> shardingValue) {
        Map<String, Collection<Comparable<?>>> columnNameAndShardingValuesMap =
                shardingValue.getColumnNameAndShardingValuesMap();

        String logicTableName = shardingValue.getLogicTableName();

        // 获取分片键的值
        Collection<Comparable<?>> userIdValues = columnNameAndShardingValuesMap.get("user_id");
        Collection<Comparable<?>> orderTimeValues = columnNameAndShardingValuesMap.get("order_time");

        Set<String> result = new HashSet<>();

        // 处理精确查询
        if (shardingValue.getColumnNameAndRangeValuesMap().isEmpty()) {
            for (Comparable<?> userId : userIdValues) {
                for (Comparable<?> orderTime : orderTimeValues) {
                    String targetName = calculateTargetName(
                            logicTableName,
                            (Long) userId,
                            (Date) orderTime
                    );
                    if (availableTargetNames.contains(targetName)) {
                        result.add(targetName);
                    }
                }
            }
        } else {
            // 处理范围查询,返回所有可能的分片
            result.addAll(availableTargetNames);
        }

        return result;
    }

    private String calculateTargetName(String logicTableName, Long userId, Date orderTime) {
        // 自定义复合分片逻辑
        // 例如:按用户地区分库,按订单月份分表

        // 1. 根据user_id确定数据库
        int dbIndex = calculateDbIndex(userId);

        // 2. 根据order_time确定表
        int tableIndex = calculateTableIndex(orderTime);

        return String.format("ds%d.%s_%d", dbIndex, logicTableName, tableIndex);
    }

    private int calculateDbIndex(Long userId) {
        // 根据用户所在地区或其他属性分库
        // 这里简单按奇偶分
        return (int) (userId % 2);
    }

    private int calculateTableIndex(Date orderTime) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(orderTime);
        int month = calendar.get(Calendar.MONTH);

        // 按月分表,每6个月一张表
        return month / 6;
    }

    @Override
    public Properties getProps() {
        return props;
    }

    @Override
    public void init() {

    }

    @Override
    public String getType() {
        return "COMPLEX_SHARDING";
    }
}
6.4.基于日期的分表策略
java 复制代码
package com.yoyo.sharding.algorithm;

import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Properties;

/**
 * 基于日期的分表策略
 * 按月分表:t_order_202312, t_order_202401, ...
 */
public class DateShardingAlgorithm implements StandardShardingAlgorithm<Date> {

    private Properties props = new Properties();
    private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM");

    @Override
    public String doSharding(Collection<String> availableTargetNames,
                             PreciseShardingValue<Date> shardingValue) {
        Date createTime = shardingValue.getValue();
        String logicTableName = shardingValue.getLogicTableName();

        // 将Date转换为LocalDate
        LocalDate localDate = convertToLocalDate(createTime);

        // 生成表后缀:年月,如 202312
        String tableSuffix = localDate.format(formatter);

        // 查找对应的表
        String targetTable = logicTableName + "_" + tableSuffix;
        for (String each : availableTargetNames) {
            if (each.equals(targetTable)) {
                return each;
            }
        }

        // 如果表不存在,创建默认表或抛出异常
        // 这里可以根据配置创建新表或路由到默认表
        return logicTableName + "_default";
    }

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames,
                                         RangeShardingValue<Date> shardingValue) {
        // 处理时间范围查询
        Date lower = (Date) shardingValue.getValueRange().lowerEndpoint();
        Date upper = (Date) shardingValue.getValueRange().upperEndpoint();

        LocalDate startDate = convertToLocalDate(lower);
        LocalDate endDate = convertToLocalDate(upper);

        Collection<String> result = new ArrayList<>();

        // 获取时间范围内的所有表
        LocalDate current = startDate;
        while (!current.isAfter(endDate)) {
            String tableName = shardingValue.getLogicTableName() + "_" + current.format(formatter);
            if (availableTargetNames.contains(tableName)) {
                result.add(tableName);
            }
            current = current.plusMonths(1);
        }

        return result;
    }

    private LocalDate convertToLocalDate(Date date) {
        if (date == null) {
            return LocalDate.now();
        }
        return new java.sql.Timestamp(date.getTime()).toLocalDateTime().toLocalDate();
    }

    @Override
    public Properties getProps() {
        return props;
    }

    @Override
    public void init() {

    }

    @Override
    public String getType() {
        return "DATE_SHARDING";
    }
}
6.5.application.yml 配置
yaml 复制代码
spring:
  shardingsphere:
    # 数据源配置 - 两个数据库实例
    datasource:
      names: ds0,ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.100.108:5866/test_db_0
        username: sysdba
        password: Qwer@12345
        hikari:
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          maximum-pool-size: 20
          minimum-idle: 5
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.100.108:5866/test_db_1
        username: sysdba
        password: Qwer@12345
        hikari:
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          maximum-pool-size: 20
          minimum-idle: 5

    # 分片规则配置
    rules:
      sharding:
        # 分表配置
        tables:
          t_order:
            # 实际数据节点:ds0.t_order_0, ds0.t_order_1, ds1.t_order_0, ds1.t_order_1
            actual-data-nodes: ds$->{0..1}.t_order_$->{0..1}

            # 分库策略
            database-strategy:
              standard:
                sharding-column: user_id
                # 使用自定义分库算法
                sharding-algorithm-name: custom-database-sharding

            # 表表策略
            table-strategy:
              standard:
                sharding-column: order_id
                # 使用自定义分表算法
                sharding-algorithm-name: custom-table-sharding

        # 分片算法
        sharding-algorithms:
          # 自定义分库算法
          custom-database-sharding:
            type: CLASS_BASED
            props:
              strategy: standard
              algorithmClassName: com.yoyo.sharding.algorithm.CustomDatabaseShardingAlgorithm
          # 自定义分表算法
          custom-table-sharding:
            type: CLASS_BASED
            props:
              strategy: standard
              algorithmClassName: com.yoyo.sharding.algorithm.CustomTableShardingAlgorithm

          # 复合分片算法
          complex-sharding:
            type: CLASS_BASED
            props:
              strategy: complex
              algorithmClassName: com.yoyo.sharding.algorithm.ComplexShardingAlgorithm

          # 日期分片算法
          date-sharding:
            type: CLASS_BASED
            props:
              strategy: standard
              algorithmClassName: com.yoyo.sharding.algorithm.DateShardingAlgorithm

    # 属性配置
    props:
      sql-show: true  # 显示SQL
      sql-simple: true # 简化SQL日志

# MyBatis Plus 配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

# 应用配置
server:
  port: 8080
6.6.测试用例
java 复制代码
@Test
void testCustomSharding() {
    List<Order> orders = new ArrayList<>();

    // 生成测试数据,验证自定义分片逻辑
    for (int i = 0; i < 100; i++) {
        Order order = new Order();

        // 使用自定义的ID生成策略
        // 或者让分片算法处理分片逻辑
        long orderId = 1000000000L + i;
        long userId = (long) (Math.random() * 1000);

        order.setOrderId(orderId);
        order.setUserId(userId);
        order.setOrderNo("ORDER_" + System.currentTimeMillis() + "_" + i);
        order.setAmount(BigDecimal.valueOf((i + 1) * 100));
        order.setStatus(i % 4);

        orders.add(order);

        // 打印分片信息
        String expectedDb = (userId % 2 == 0) ? "ds0" : "ds1";
        String expectedTable = (orderId % 2 == 0) ? "t_order_0" : "t_order_1";
        log.info("订单{}: userId={}({}库), orderId={}({}表)",
                i, userId, expectedDb, orderId, expectedTable);
    }

    // 批量插入
    orderService.batchCreateOrders(orders);

    log.info("自定义分片测试完成,共插入{}条记录", orders.size());
}

@Test
void testQueryWithCustomSharding() {
    List<Order> allOrders = orderService.queryAllOrders();
    log.info("所有订单数量: {}", allOrders.size());

    // 统计各分片表的订单数量
    long count0 = allOrders.stream()
            .filter(order -> order.getUserId() % 2 == 0 && order.getOrderId() % 2 == 0)
            .count();
    long count1 = allOrders.stream()
            .filter(order -> order.getUserId() % 2 == 0 && order.getOrderId() % 2 == 1)
            .count();
    long count2 = allOrders.stream()
            .filter(order -> order.getUserId() % 2 == 1 && order.getOrderId() % 2 == 0)
            .count();
    long count3 = allOrders.stream()
            .filter(order -> order.getUserId() % 2 == 1 && order.getOrderId() % 2 == 1)
            .count();

    log.info("test_db_0.t_order_0 表订单数: {}", count0);
    log.info("test_db_0.t_order_1 表订单数: {}", count1);
    log.info("test_db_1.t_order_0 表订单数: {}", count2);
    log.info("test_db_1.t_order_1 表订单数: {}", count3);
}

7.数据加密

7.1.数据准备
sql 复制代码
--创建数据库
create database encryption_db;

-- 切换到 encryption_db 数据库
CREATE TABLE t_user(
  id SERIAL PRIMARY KEY,
  name VARCHAR NOT NULL,
  phone VARCHAR,
  email VARCHAR,
  id_card VARCHAR,
  password VARCHAR
);
7.2.主配置文件(application.yaml)
yaml 复制代码
spring:
  shardingsphere:
    datasource:
      names: ds0
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.100.108:5866/encryption_db
        username: sysdba
        password: Qwer@12345

    rules:
      encrypt:
        encryptors:
          aes_encryptor:
            type: CUSTOM_AES
          base64_encryptor:
            type: CUSTOM_BASE64
          md5_encryptor:
            type: CUSTOM_MD5
          sm4_encryptor:
            type: CUSTOM_SM4

        tables:
          t_user:
            columns:
              email:
                cipher-column: email
                encryptor-name: aes_encryptor
              phone:
                cipher-column: phone
                encryptor-name: base64_encryptor
              password:
                cipher-column: password
                encryptor-name: sm4_encryptor
              id_card:
                cipher-column: id_card
                encryptor-name: md5_encryptor

    props:
      sql-show: true
      query-with-cipher-column: true  # 是否使用密文列查询

# MyBatis Plus 配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

# 应用配置
server:
  port: 8080
7.3.自定义加密算法
7.3.1.AES加密实现

CustomAesEncryptAlgorithm.java

java 复制代码
package com.yoyo.sharding.encrypt;

import org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm;
import org.apache.shardingsphere.encrypt.spi.context.EncryptContext;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;

public class CustomAesEncryptAlgorithm implements EncryptAlgorithm<String,String> {
    private static final String ALGORITHM = "AES";
    private static final String SECRET_KEY = "1234567890123456";

    @Override
    public void init() {

    }

    @Override
    public String encrypt(String plainValue, EncryptContext encryptContext) {
        if (plainValue == null) return null;
        try {
            KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(SECRET_KEY.getBytes());
            keyGen.init(128, secureRandom);
            SecretKey secretKeySpec = new SecretKeySpec(keyGen.generateKey().getEncoded(), ALGORITHM);

            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            byte[] encrypted = cipher.doFinal(plainValue.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(encrypted);
        } catch (Exception e) {
            throw new RuntimeException("AES encryption failed", e);
        }
    }

    @Override
    public String decrypt(String cipherValue, EncryptContext encryptContext) {
        if (cipherValue == null) return null;
        try {

            KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(SECRET_KEY.getBytes());
            keyGen.init(128, secureRandom);
            SecretKey secretKeySpec = new SecretKeySpec(keyGen.generateKey().getEncoded(), ALGORITHM);

            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);

            byte[] decrypted = cipher.doFinal(hexToBytes(cipherValue));
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("AES decryption failed", e);
        }
    }

    @Override
    public String getType() {
        return "CUSTOM_AES"; // 必须唯一,用于 YAML 中引用
    }

    // 字节数组转十六进制字符串
    private String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }

    // 十六进制字符串转字节数组
    private byte[] hexToBytes(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                    + Character.digit(hex.charAt(i + 1), 16));
        }
        return data;
    }

}
7.3.2.BASE64加密实现

CustomBase64EncryptAlgorithm.java

java 复制代码
package com.yoyo.sharding.encrypt;

import org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm;
import org.apache.shardingsphere.encrypt.spi.context.EncryptContext;
import org.springframework.stereotype.Component;

import java.util.Base64;

@Component
public class CustomBase64EncryptAlgorithm implements EncryptAlgorithm<String,String> {
    @Override
    public void init() {

    }

    @Override
    public String encrypt(String plainValue, EncryptContext encryptContext) {
        if (plainValue == null) return null;
        return Base64.getEncoder().encodeToString(plainValue.getBytes());
    }

    @Override
    public String decrypt(String cipherValue, EncryptContext encryptContext) {
        if (cipherValue == null) return null;
        return new String(Base64.getDecoder().decode(cipherValue));
    }

    @Override
    public String getType() {
        return "CUSTOM_BASE64"; // 必须唯一,用于 YAML 中引用
    }

}
7.3.3.MD5加密实现

CustomMd5EncryptAlgorithm.java

java 复制代码
package com.yoyo.sharding.encrypt;

import org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm;
import org.apache.shardingsphere.encrypt.spi.context.EncryptContext;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

public class CustomMd5EncryptAlgorithm implements EncryptAlgorithm<String,String> {

    private static final String ALGORITHM = "MD5";

    @Override
    public void init() {

    }

    @Override
    public String encrypt(String plainValue, EncryptContext encryptContext) {
        if (plainValue == null) {
            return null;
        }
        try {
            MessageDigest md = MessageDigest.getInstance(ALGORITHM);
            byte[] digest = md.digest(plainValue.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(digest);

        } catch (Exception e) {
            throw new RuntimeException("MD5 encryption failed", e);
        }
    }

    @Override
    public String decrypt(String cipherValue, EncryptContext encryptContext) {
        return cipherValue;
    }

    @Override
    public String getType() {
        return "CUSTOM_MD5";
    }

    // 字节数组转十六进制字符串
    private String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }
}
7.3.4.SM4加密实现

CustomSm4EncryptAlgorithm.java

java 复制代码
package com.yoyo.sharding.encrypt;

import org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm;
import org.apache.shardingsphere.encrypt.spi.context.EncryptContext;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

@Component
public class CustomSm4EncryptAlgorithm implements EncryptAlgorithm<String,String> {

    private static final String ALGORITHM = "SM4";
    private static final String TRANSFORMATION = "SM4/ECB/PKCS5Padding";
    private static final String SECRET_KEY = "1234567890abcdef";

    @Override
    public void init() {

    }

    @Override
    public String encrypt(String plainValue, EncryptContext encryptContext) {
        if (plainValue == null) {
            return null;
        }
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM, "BC");
            keyGenerator.init(128);
            SecretKey sm4Key = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);

            Cipher cipher = Cipher.getInstance(TRANSFORMATION,"BC");
            cipher.init(Cipher.ENCRYPT_MODE, sm4Key);
            byte[] encrypted = cipher.doFinal(plainValue.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(encrypted);
        } catch (Exception e) {
            throw new RuntimeException("SM4 encryption failed", e);
        }
    }

    @Override
    public String decrypt(String cipherValue, EncryptContext encryptContext) {
        if (cipherValue == null) {
            return null;
        }
        try {
            SecretKey sm4Key = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
            Cipher cipher = Cipher.getInstance(TRANSFORMATION, "BC");
            cipher.init(Cipher.DECRYPT_MODE, sm4Key);
            byte[] decrypted = cipher.doFinal(hexToBytes(cipherValue));
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("SM4 decryption failed", e);
        }
    }

    @Override
    public String getType() {
        return "CUSTOM_SM4";
    }

    // 字节数组转十六进制字符串
    private String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }

    // 十六进制字符串转字节数组
    private byte[] hexToBytes(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                                  + Character.digit(hex.charAt(i + 1), 16));
        }
        return data;
    }

}
7.4.SPI注册自定义算法配置

resources/META-INF/services/org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm

plain 复制代码
com.yoyo.sharding.encrypt.CustomBase64EncryptAlgorithm
com.yoyo.sharding.encrypt.CustomAesEncryptAlgorithm
com.yoyo.sharding.encrypt.CustomSm4EncryptAlgorithm
com.yoyo.sharding.encrypt.CustomMd5EncryptAlgorithm
7.5.实体类
java 复制代码
package com.yoyo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;

    private String name;

    private String phone;    // 会被 Base64 加密

    private String email;    // 会被 AES 加密

    @TableField(value = "id_card")
    private String idCard;   // 会被 md5 加密

    private String password; // 会被 SM4 加密

}
7.6.Mapper接口
java 复制代码
package com.yoyo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yoyo.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User>{

}
7.7.Service接口
java 复制代码
package com.yoyo.service;

import com.yoyo.entity.User;

public interface UserService {
    boolean saveUser(User u);
    User getUserById(Long id);
}
7.8.Service实现
java 复制代码
package com.yoyo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yoyo.entity.User;
import com.yoyo.mapper.UserMapper;
import com.yoyo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>  implements UserService {
    @Override
    public boolean saveUser(User u) {
        return this.save(u);
    }

    @Override
    public User getUserById(Long id) {
        return this.getById(id);
    }
}
7.9.测试用例
java 复制代码
@Test
void testUserInsert() {
    User u = new User();
    u.setId(1L);
    u.setName("zhangsan");
    u.setEmail("zhangsan@example.com");
    u.setPhone("18888888888");
    u.setIdCard("371425999999999999");
    u.setPassword("1111111111");

    if(userService.saveUser(u)){
        log.info("数据写入成功");
    } else {
        log.info("数据写入失败");
    }

}

@Test
void testGetUserById() {
    Long userId = 1L;
    User user = userService.getUserById(userId);
    if(user != null){
        System.out.println("+++++++++++++++++++++++++++");
        System.out.println("用户ID:" + user.getId());
        System.out.println("用户NAME:" + user.getName());
        System.out.println("用户PASSWORD:" + user.getPassword());
        System.out.println("用户EMAIL:" + user.getEmail());
        System.out.println("用户PHONE:" + user.getPhone());
        System.out.println("用户ID_CARD:" + user.getIdCard());
        System.out.println("+++++++++++++++++++++++++++");
    }

}

8.读写分离

8.1.数据准备
sql 复制代码
--创建数据库
create database master_db;
create database slave1_db;
create database slave2_db;

-- 在主库和所有从库中执行
CREATE TABLE IF NOT EXISTS t_role (
  id BIGSERIAL PRIMARY KEY,
  role_name VARCHAR(50) NOT NULL,
  create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  deleted INTEGER DEFAULT 0
);
8.2.主配置文件(application.yaml)
yaml 复制代码
spring:
  shardingsphere:
    datasource:
      names: master,slave1,slave2

      master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.100.108:5866/master_db
        username: sysdba
        password: Qwer@12345
        hikari:
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          maximum-pool-size: 20
          minimum-idle: 5

      slave1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.100.108:5866/slave1_db
        username: sysdba
        password: Qwer@12345
        hikari:
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          maximum-pool-size: 20
          minimum-idle: 5

      slave2:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.100.108:5866/slave2_db
        username: sysdba
        password: Qwer@12345
        hikari:
          connection-timeout: 30000
          idle-timeout: 600000
          max-lifetime: 1800000
          maximum-pool-size: 20
          minimum-idle: 5

    rules:
      readwrite-splitting:
        data-sources:
          rw_ds:  # 读写分离数据源名称(逻辑数据源)
           type: Static # 静态读写分离配置
           props:
             # 写数据源名称(必须配置)
             write-data-source-name: master
             # 读数据源名称列表(必须配置,多个用逗号分隔)
             read-data-source-names: slave1,slave2
            # 负载均衡算法名称(可选)
           load-balancer-name: round_robin

        # 负载均衡算法配置
        load-balancers:
          # 轮询算法
          round_robin:
            type: ROUND_ROBIN
          # 随机访问算法
          random:
            type: RANDOM

          # 权重访问算法
          weight:
            type: WEIGHT
            props:
              slave1: 2
              slave2: 1


    props:
      sql-show: true
      query-with-cipher-column: true  # 是否使用密文列查询

# MyBatis Plus 配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

# 应用配置
server:
  port: 8080
8.3.实体类
java 复制代码
package com.yoyo.entity;


import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("t_role")
public class Role {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @TableField("role_name")
    private String roleName;

    @TableField(value = "create_time")
    private LocalDateTime createTime;

    @TableField(value = "update_time")
    private LocalDateTime updateTime;

    private Integer deleted;
}
8.4.Mapper接口
java 复制代码
package com.yoyo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yoyo.entity.Role;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface RoleMapper extends BaseMapper<Role> {
}
8.5.Service接口
java 复制代码
package com.yoyo.service;

import com.yoyo.entity.Role;

import java.util.List;

public interface RoleService {
    /**
     * 添加角色(写操作)
     */
    boolean addUser(Role role);

    /**
     * 根据角色名称查询(读操作)
     */
    Role getRoleByName(String name);

    /**
     * 更新角色信息(写操作)
     */
    boolean updateRole(Role role);

    /**
     * 删除角色(写操作)
     */
    boolean deleteRole(Long id);
}
8.6.Service实现
java 复制代码
package com.yoyo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yoyo.entity.Order;
import com.yoyo.entity.Role;
import com.yoyo.mapper.OrderMapper;
import com.yoyo.mapper.RoleMapper;
import com.yoyo.service.OrderService;
import com.yoyo.service.RoleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

@Slf4j
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    @Override
    public boolean addUser(Role role) {
        return this.save(role);
    }

    @Override
    public Role getRoleByName(String name) {
        QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_name",name);
        return this.getOne(queryWrapper);
    }

    @Override
    public boolean updateRole(Role role) {
        return this.updateById(role);
    }

    @Override
    public boolean deleteRole(Long id) {
        return this.removeById(id);
    }
}
8.7.测试用例
java 复制代码
@Test
void testRoleInsert() {
    Role role = new Role();
    role.setId(1L);
    role.setRoleName("管理员");
    role.setCreateTime(LocalDateTime.now());

    if(roleService.save(role)){
        log.info("数据写入成功");
    } else {
        log.info("数据写入失败");
    }
}

@Test
void testGetRoleByName() {
    String roleName = "管理员";
    Role role = roleService.getRoleByName(roleName);
    if(role != null){
        System.out.println("+++++++++++++++++++++++++++");
        System.out.println("角色ID:" + role.getId());
        System.out.println("用户NAME:" + role.getRoleName());
        System.out.println("+++++++++++++++++++++++++++");
    }
}

@Test
void testUpdateRoleById() {
    Role role = new Role();
    role.setId(1L);
    role.setRoleName("审计员");
    role.setUpdateTime(LocalDateTime.now());
    if(roleService.updateRole(role)){
        log.info("数据修改成功");
    } else {
        log.info("数据修改失败");
    }
}

@Test
void testDeleteRoleById() {
    if(roleService.deleteRole(1L)){
        log.info("数据删除成功");
    } else {
        log.info("数据删除失败");
    }
}
相关推荐
lkbhua莱克瓦242 小时前
基础-MySQL概述
java·开发语言·数据库·笔记·mysql
姓蔡小朋友2 小时前
MySQL增删查改、多表查询
数据库·mysql
月明长歌2 小时前
【码道初阶】Leetcode136:只出现一次的数字:异或一把梭 vs HashMap 计数(两种解法完整复盘)
java·数据结构·算法·leetcode·哈希算法
Swift社区2 小时前
LeetCode 456 - 132 模式
java·算法·leetcode
Knight_AL2 小时前
Maven <dependencyManagement>:如何在多模块项目中集中管理依赖版本
java·数据库·maven
狼与自由2 小时前
excel 导入 科学计数法问题处理
java·前端·excel
TAEHENGV2 小时前
导入导出模块 Cordova 与 OpenHarmony 混合开发实战
android·javascript·数据库
建群新人小猿2 小时前
陀螺匠企业助手 运行环境
java·大数据·人工智能·docker·php
写代码的小阿帆2 小时前
Java本地缓存技术——Guava、Caffeine
java·缓存·guava