Bootstrap

SpringBoot框架简单整合ShardingSphere-JDBC实现MySQL分库分表和读写分离及加密混合

1. 主从配置

1.1 主机1(IP:192.168.186.77)

1.1.1 docker-compose.yml

version: '3.8'

services:
  mysql-master:
    image: mysql:latest
    container_name: mysql-master
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_USER: master
      MYSQL_PASSWORD: 123456
      MYSQL_DATABASE: db1  
    ports:
      - "3306:3306"
    volumes:
      - mysql-master-data:/var/lib/mysql
    command: --server-id=1 --log-bin=mysql-bin --binlog-format=ROW
    cap_add:
      - SYS_NICE
    security_opt:
      - seccomp:unconfined

  mysql-slave:
    image: mysql:latest
    container_name: mysql-slave
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_USER: slave
      MYSQL_PASSWORD: 123456
      MYSQL_DATABASE: db1
    ports:
      - "3307:3306"
    volumes:
      - mysql-slave-data:/var/lib/mysql
    command: --server-id=2 --log-bin=mysql-bin --binlog-format=ROW --relay-log=relay-bin --relay-log-index=relay-bin.index
    depends_on:
      - mysql-master
    cap_add:
      - SYS_NICE
    security_opt:
      - seccomp:unconfined

volumes:
  mysql-master-data:
  mysql-slave-data:

说明:端口3306充当主库,端口3307充当从库,创建的时候同时创建新用户和密码,如用户master,同时创建的时候也创建数据库db1。在MySQL 的主从复制架构中,每个实例都必须有一个唯一的 server-id。即使实例位于不同的 IP 地址上,每个实例的 server-id 也必须是唯一的。

1.1.2 启动主从数据库

docker-compose up -d

1.1.3 主数据库用户授权

# 给mater用户授全部权限
GRANT ALL PRIVILEGES ON *.* TO 'master'@'%';
FLUSH PRIVILEGES;

1.1.4 配置主从数据库

1.1.4.1 配置主数据库
# 创建主从配置之间的用户
CREATE USER 'master_slave'@'%' IDENTIFIED BY '123456' REQUIRE SSL;
GRANT REPLICATION SLAVE ON *.* TO 'master_slave'@'%';
FLUSH PRIVILEGES;
# 查看FILE 和 Position 文件
SHOW MASTER STATUS;

说明:FILE 和 Position 是配置从数据库的关键,需要自行查看并修改对应坐标。 

1.1.4.2 配置从数据库 
CHANGE MASTER TO
    MASTER_HOST ='192.168.186.77', # 主服务器的 IP 地址
    MASTER_USER ='master_slave', # 主服务器上配置的复制用户
    MASTER_PASSWORD ='123456', # 复制用户的密码
    MASTER_LOG_FILE ='mysql-bin.000003', # 主服务器的日志文件名
    MASTER_LOG_POS =1358, # 日志文件的位置
    MASTER_SSL=1;
START SLAVE;
SHOW SLAVE STATUS;

 说明:出现 Slave_IO_Running 和 Slave_SQL_Running 都是YES说明主从配置成功。

 1.1.4.3 创建数据库表
use db1;
CREATE TABLE t_order_0
(
    order_id   INT PRIMARY KEY,
    user_id    INT,
    order_date DATE,
    status     VARCHAR(255)
);

CREATE TABLE t_order_1
(
    order_id   INT PRIMARY KEY,
    user_id    INT,
    order_date DATE,
    status     VARCHAR(255)
);

 说明 :需要在不同IP的主数据库上执行该数据库语句,创建真实物理表,从数据库会自动同步复制数据库的结构。ShardingSphere会创建逻辑数据库数据表根据分库规则实现分库分表。

注意:主机2和主机3的配置流程几乎相同,如果个人能力允许可以跳过该部分。

1.2 主机2(IP:192.168.186.216)

1.2.1 docker-compose.yml

version: '3.8'

services:
  mysql-master:
    image: mysql:latest
    container_name: mysql-master
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_USER: master
      MYSQL_PASSWORD: 123456
      MYSQL_DATABASE: db2  
    ports:
      - "3306:3306"
    volumes:
      - mysql-master-data:/var/lib/mysql
    command: --server-id=3 --log-bin=mysql-bin --binlog-format=ROW
    cap_add:
      - SYS_NICE
    security_opt:
      - seccomp:unconfined

  mysql-slave:
    image: mysql:latest
    container_name: mysql-slave
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_USER: slave
      MYSQL_PASSWORD: 123456
      MYSQL_DATABASE: db2  
    ports:
      - "3307:3306"
    volumes:
      - mysql-slave-data:/var/lib/mysql
    command: --server-id=4 --log-bin=mysql-bin --binlog-format=ROW --relay-log=relay-bin --relay-log-index=relay-bin.index
    depends_on:
      - mysql-master
    cap_add:
      - SYS_NICE
    security_opt:
      - seccomp:unconfined

volumes:
  mysql-master-data:
  mysql-slave-data:

说明:通过server-id 区分不同的主从数据库。

1.2.2 启动主从数据库

docker-compose up -d

1.2.3 主数据库用户授权

# 给mater用户授全部权限
GRANT ALL PRIVILEGES ON *.* TO 'master'@'%';
FLUSH PRIVILEGES;

1.2.4 配置主从数据库

1.2.4.1 配置主数据库
# 创建主从配置之间的用户
CREATE USER 'master_slave'@'%' IDENTIFIED BY '123456' REQUIRE SSL;
GRANT REPLICATION SLAVE ON *.* TO 'master_slave'@'%';
FLUSH PRIVILEGES;
# 查看FILE 和 Position 文件
SHOW MASTER STATUS;

说明:FILE 和 Position 是配置从数据库的关键,需要自行查看并修改对应坐标。

 1.2.4.2 配置从数据库 
CHANGE MASTER TO
    MASTER_HOST ='192.168.186.216', # 主服务器的 IP 地址
    MASTER_USER ='master_slave', # 主服务器上配置的复制用户
    MASTER_PASSWORD ='123456', # 复制用户的密码
    MASTER_LOG_FILE ='mysql-bin.000003', # 主服务器的日志文件名
    MASTER_LOG_POS =1781, # 日志文件的位置
    MASTER_SSL=1;
START SLAVE;
SHOW SLAVE STATUS;

说明:出现 Slave_IO_Running 和 Slave_SQL_Running 都是YES说明主从配置成功。

1.2.4.3 创建数据库表
use db2;
CREATE TABLE t_order_0
(
    order_id   INT PRIMARY KEY,
    user_id    INT,
    order_date DATE,
    status     VARCHAR(255)
);

CREATE TABLE t_order_1
(
    order_id   INT PRIMARY KEY,
    user_id    INT,
    order_date DATE,
    status     VARCHAR(255)
);

说明 :需要在不同IP的主数据库上执行该数据库语句,创建真实物理表,从数据库会自动同步复制数据库的结构。ShardingSphere会创建逻辑数据库数据表根据分库规则实现分库分表。

1.3 主机3(IP:192.168.186.18) 

1.3.1 docker-compose.yml

version: '3.8'

services:
  mysql-master:
    image: mysql:latest
    container_name: mysql-master
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_USER: master
      MYSQL_PASSWORD: 123456
      MYSQL_DATABASE: db3  
    ports:
      - "3306:3306"
    volumes:
      - mysql-master-data:/var/lib/mysql
    command: --server-id=5 --log-bin=mysql-bin --binlog-format=ROW
    cap_add:
      - SYS_NICE
    security_opt:
      - seccomp:unconfined

  mysql-slave:
    image: mysql:latest
    container_name: mysql-slave
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_USER: slave
      MYSQL_PASSWORD: 123456
      MYSQL_DATABASE: db3 
    ports:
      - "3307:3306"
    volumes:
      - mysql-slave-data:/var/lib/mysql
    command: --server-id=6 --log-bin=mysql-bin --binlog-format=ROW --relay-log=relay-bin --relay-log-index=relay-bin.index
    depends_on:
      - mysql-master
    cap_add:
      - SYS_NICE
    security_opt:
      - seccomp:unconfined

volumes:
  mysql-master-data:
  mysql-slave-data:

说明:通过server-id不同的主从数据库。

1.3.2 启动主从数据库

docker-compose up -d

1.3.3 主数据库用户授权

# 给mater用户授全部权限
GRANT ALL PRIVILEGES ON *.* TO 'master'@'%';
FLUSH PRIVILEGES;

1.3.4 配置主从数据库

1.3.4.1 配置主数据库
# 创建主从配置之间的用户
CREATE USER 'master_slave'@'%' IDENTIFIED BY '123456' REQUIRE SSL;
GRANT REPLICATION SLAVE ON *.* TO 'master_slave'@'%';
FLUSH PRIVILEGES;
# 查看FILE 和 Position 文件
SHOW MASTER STATUS;

说明:FILE 和 Position 是配置从数据库的关键,需要自行查看并修改对应坐标。  

1.3.4.2 配置从数据库  

说明:出现 Slave_IO_Running 和 Slave_SQL_Running 都是YES说明主从配置成功。

1.3.4.3 创建数据库表
use db3;
CREATE TABLE t_order_0
(
    order_id   INT PRIMARY KEY,
    user_id    INT,
    order_date DATE,
    status     VARCHAR(255)
);

CREATE TABLE t_order_1
(
    order_id   INT PRIMARY KEY,
    user_id    INT,
    order_date DATE,
    status     VARCHAR(255)
);

1.4 其他

ERROR:

Coordinator stopped because there were error(s) in the worker(s). The most recent failure being: Worker 1 failed executing transaction 'ANONYMOUS' at master log mysql-bin.000003, end_log_pos 3890. See error log and/or performance_schema.replication_applier_status_by_worker table for more details about this failure or others, if any.

        如果遇到主从复制错误(我遇到是给用户重新授权的时候出现的),可以尝试以下解决方法: 

-- 停止从库复制
STOP SLAVE;

-- 跳过一个错误的事务
SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;

-- 重新启动从库复制
START SLAVE;

2. ShardingSphere-JDBC

2.1 项目结构

2.2 Maven依赖 

<?xml version="1.0" encoding="UTF-8"?>
<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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>org.example</groupId>
    <artifactId>ShardingSphere-JDBC</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc</artifactId>
            <version>5.5.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.shardingsphere</groupId>
                    <artifactId>shardingsphere-test-util</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2.3 Order.java

package org.example.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;
import java.util.Date;

@Entity
@Table(name = "t_order")
@Data
public class Order {
    @Id
    @Column(name = "order_id")
    private int orderId;

    @Column(name = "user_id")
    private int userId;

    @Column(name = "order_date")
    private Date orderDate;

    @Column(name = "status")
    private String status;
}

 2.4 orderRepository.java

package org.example.repository;

import org.example.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface orderRepository extends JpaRepository<Order, Long> {
}

2.5 ShardingSphereConfig.java

package org.example.config;

import org.apache.shardingsphere.driver.api.yaml.YamlShardingSphereDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import javax.sql.DataSource;


import java.io.IOException;
import java.sql.SQLException;


@Configuration
public class ShardingSphereConfig{
    @Value("shardingsphere.yaml")
    private ClassPathResource config;
    @Bean
    public DataSource dataSource() throws SQLException, IOException {
        return YamlShardingSphereDataSourceFactory.createDataSource(config.getContentAsByteArray());
    }
}

说明:通过@Value注入shardingsphere.yaml并创建dataSource的Bean。

数据分片 :: ShardingSphere 

2.6 application.yaml 

spring:
  application:
      name: ShardingSphere-JDBC

2.7 logback.xml 

<configuration>
    <!-- 定义过滤器,用于过滤出包含 "Logic SQL:" 或 "Actual SQL:" 的日志消息 -->
    <appender name="SQL_FILTER" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 只打印消息部分 -->
            <pattern>%msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
            <evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
                <expression>
                    return message.contains("Logic SQL:") || message.contains("Actual SQL:");
                </expression>
            </evaluator>
            <OnMismatch>DENY</OnMismatch>
            <OnMatch>ACCEPT</OnMatch>
        </filter>
    </appender>

    <!-- 配置针对 ShardingSphere-SQL 的日志输出 -->
    <logger name="ShardingSphere-SQL" level="INFO" additivity="false">
        <appender-ref ref="SQL_FILTER" />
    </logger>
</configuration>

2.8 shardingsphere.yaml

2.8.1 逻辑数据库名称

databaseName: my_database # 数据库配置名称。

2.8.2 数据源配置

dataSources:
  ds_77_master:
    # 主数据库服务器的配置,IP 为 192.168.186.77,端口为 3306。
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    # HikariCP 数据源的完整类名。

    url: jdbc:mysql://192.168.186.77:3306/db1?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    # 用于连接 MySQL 数据库的 JDBC URL。
    # serverTimezone=UTC 确保服务器时区设置为 UTC。
    # useSSL=false 禁用 SSL。
    # allowPublicKeyRetrieval=true 允许客户端从服务器检索公钥。

    username: master
    # 用于连接数据库的用户名。

    password: 123456
    # 数据库用户的密码。

    connectionTimeoutMilliseconds: 30000
    # 客户端等待从连接池获取连接的最大时间(以毫秒为单位)。
    # 默认值为 30000 毫秒(30 秒)。

    idleTimeoutMilliseconds: 60000
    # 连接在连接池中闲置的最长时间(以毫秒为单位)。
    # 默认值为 600000 毫秒(10 分钟)。

    maxLifetimeMilliseconds: 1800000
    # 连接在连接池中的最长存活时间(以毫秒为单位)。
    # 默认值为 1800000 毫秒(30 分钟)。

    maxPoolSize: 50
    # 连接池将维持的最大连接数。
    # 默认值为 10。

    minPoolSize: 1
    # 连接池将维持的最小闲置连接数。
    # 默认值为 1。

  ds_77_slave:
    # 从数据库服务器的配置,IP 为 192.168.186.77,端口为 3307。
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.186.77:3307/db1?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: slave
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1

  ds_216_master:
    # 主数据库服务器的配置,IP 为 192.168.186.216,端口为 3306。
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.186.216:3306/db2?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: master
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1

  ds_216_slave:
    # 从数据库服务器的配置,IP 为 192.168.186.216,端口为 3307。
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.186.216:3307/db2?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: slave
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1

  ds_18_master:
    # 主数据库服务器的配置,IP 为 192.168.186.18,端口为 3306。
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.186.18:3306/db3?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: master
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1

  ds_18_slave:
    # 从数据库服务器的配置,IP 为 192.168.186.18,端口为 3307。
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.186.18:3307/db3?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: slave
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1

 2.8.3 读写分离规则

rules:
  - !READWRITE_SPLITTING
    # 配置读写分离规则。
    dataSources:
      # 定义数据源组。
      readwrite_77:
        # 名为 readwrite_77 的读写分离数据源组配置。
        writeDataSourceName: ds_77_master  # 写操作的数据源名称,指向 ds_77_master 数据源。
        readDataSourceNames:               # 读操作的数据源名称列表,这里指定为 ds_77_slave 数据源。
          - ds_77_slave
        transactionalReadQueryStrategy: DYNAMIC  # 事务性读查询策略,DYNAMIC 表示根据实际情况动态选择数据源。
        loadBalancerName: random                 # 负载均衡器名称,指向名为 random 的负载均衡策略。

      readwrite_216:
        # 名为 readwrite_216 的读写分离数据源组配置。
        writeDataSourceName: ds_216_master  # 写操作的数据源名称,指向 ds_216_master 数据源。
        readDataSourceNames:                # 读操作的数据源名称列表,这里指定为 ds_216_slave 数据源。
          - ds_216_slave
        transactionalReadQueryStrategy: DYNAMIC  # 事务性读查询策略,DYNAMIC 表示根据实际情况动态选择数据源。
        loadBalancerName: random                 # 负载均衡器名称,指向名为 random 的负载均衡策略。

      readwrite_18:
        # 名为 readwrite_18 的读写分离数据源组配置。
        writeDataSourceName: ds_18_master  # 写操作的数据源名称,指向 ds_18_master 数据源。
        readDataSourceNames:               # 读操作的数据源名称列表,这里指定为 ds_18_slave 数据源。
          - ds_18_slave
        transactionalReadQueryStrategy: DYNAMIC  # 事务性读查询策略,DYNAMIC 表示根据实际情况动态选择数据源。
        loadBalancerName: random                 # 负载均衡器名称,指向名为 random 的负载均衡策略。

    loadBalancers:
      # 定义负载均衡策略。
      random:
        # 名为 random 的负载均衡策略配置。
        type: RANDOM  # 负载均衡策略类型,这里设置为随机(RANDOM)。

说明:transactionalReadQueryStrategy对应参数 PRIMARYFIXEDDYNAMIC 对应不同的读写分离策略:

  1. PRIMARY: 只从主库读取。这通常用于需要确保读取到最新数据的情况。

  2. FIXED: 固定从库读取。这种情况下会根据配置的从库列表选择特定的从库读取数据。

  3. DYNAMIC: 动态从库读取。根据负载均衡策略动态选择从库读取数据。

     如果想让读操作从从库读取,可以配置 FIXEDDYNAMIC 策略。

2.8.4 分片规则

- !SHARDING
  # 配置分片规则。
  tables:
    t_order:
      # 针对表 t_order 的分片配置。
      actualDataNodes: readwrite_77.t_order_${0..1}, readwrite_216.t_order_${0..1}, readwrite_18.t_order_${0..1}
      # 实际数据节点,表示 t_order 表在三个数据源组(readwrite_77, readwrite_216, readwrite_18)的分片情况。
      tableStrategy:
        standard:
          shardingColumn: order_id  # 分片键为 order_id。
          shardingAlgorithmName: t_order_inline  # 分片算法名称为 t_order_inline。
      keyGenerateStrategy:
        column: order_id  # 主键生成策略,针对 order_id 列。
        keyGeneratorName: snowflake  # 主键生成器名称为 snowflake。

  defaultDatabaseStrategy:
    standard:
      # 默认数据库策略。
      shardingColumn: user_id  # 分片键为 user_id。
      shardingAlgorithmName: database_inline  # 分片算法名称为 database_inline。

  defaultTableStrategy:
    none:  # 默认表策略为 none,表示不进行默认表分片。

  bindingTables:
    - t_order  # 绑定表配置,表示 t_order 是一个绑定表。

  shardingAlgorithms:
    database_inline:
      # 分片算法名称为 database_inline。
      type: INLINE  # 算法类型为 INLINE。
      props:
        algorithm-expression: "readwrite_${(user_id % 3 == 0) ? '77' : ((user_id % 3 == 1) ? '216' : '18')}"
        # 算法表达式,根据 user_id 的值将数据分片到不同的数据源组:
        # 如果 user_id % 3 == 0,则选择 readwrite_77;
        # 如果 user_id % 3 == 1,则选择 readwrite_216;
        # 否则选择 readwrite_18。

    t_order_inline:
      # 分片算法名称为 t_order_inline。
      type: INLINE  # 算法类型为 INLINE。
      props:
        algorithm-expression: "t_order_${order_id % 2}"
        # 算法表达式,根据 order_id 的值将数据分片到 t_order_0 或 t_order_1:
        # 如果 order_id % 2 == 0,则选择 t_order_0;
        # 否则选择 t_order_1。

  keyGenerators:
    snowflake:
      # 主键生成器名称为 snowflake。
      type: SNOWFLAKE  # 主键生成器类型为 SNOWFLAKE。
      props:
        worker-id: 123  # SNOWFLAKE 算法的 worker-id 配置,值为 123。

- !ENCRYPT
  # 配置加密规则。
  encryptors:
    aes_encryptor:
      # 加密器名称为 aes_encryptor。
      type: AES  # 加密器类型为 AES(对称加密算法)。
      props:
        aes-key-value: 123456abc  # AES 加密的密钥值,必须是长度为 16、24 或 32 字节的密钥。

  tables:
    t_order:
      # 针对表 t_order 的加密配置。
      columns:
        status:
          # 针对 status 列进行加密配置。
          cipher:
            name: status  # 加密后列的名称仍为 status。
            encryptorName: aes_encryptor  # 使用名称为 aes_encryptor 的加密器进行加密。

props:
  sql-show: true  # 显示生成的 SQL 语句,便于调试。

2.8.5 加密规则

- !ENCRYPT
  # 配置加密规则。
  encryptors:
    aes_encryptor:
      # 加密器名称为 aes_encryptor。
      type: AES  # 加密器类型为 AES(对称加密算法)。
      props:
        aes-key-value: 123456abc  # AES 加密的密钥值,必须是长度为 16、24 或 32 字节的密钥。

  tables:
    t_order:
      # 针对表 t_order 的加密配置。
      columns:
        status:
          # 针对 status 列进行加密配置。
          cipher:
            name: status  # 加密后列的名称仍为 status。
            encryptorName: aes_encryptor  # 使用名称为 aes_encryptor 的加密器进行加密。

2.8.6 其他

props:
  sql-show: true  # 显示生成的 SQL 语句,便于调试。

2.8.7 完整文件 

databaseName: my_database
dataSources:
  ds_77_master:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.186.77:3306/db1?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: master
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  ds_77_slave:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.186.77:3307/db1?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: slave
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  ds_216_master:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.186.216:3306/db2?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: master
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  ds_216_slave:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.186.216:3307/db2?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: slave
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  ds_18_master:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.186.18:3306/db3?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: master
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  ds_18_slave:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://192.168.186.18:3307/db3?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: slave
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
rules:
  - !READWRITE_SPLITTING
    dataSources:
      readwrite_77:
        writeDataSourceName: ds_77_master
        readDataSourceNames:
          - ds_77_slave
        transactionalReadQueryStrategy: DYNAMIC
        loadBalancerName: random
      readwrite_216:
        writeDataSourceName: ds_216_master
        readDataSourceNames:
          - ds_216_slave
        transactionalReadQueryStrategy: DYNAMIC
        loadBalancerName: random
      readwrite_18:
        writeDataSourceName: ds_18_master
        readDataSourceNames:
          - ds_18_slave
        transactionalReadQueryStrategy: DYNAMIC
        loadBalancerName: random
    loadBalancers:
      random:
        type: RANDOM
  - !SHARDING
    tables:
      t_order:
        actualDataNodes: readwrite_77.t_order_${0..1}, readwrite_216.t_order_${0..1}, readwrite_18.t_order_${0..1}
        tableStrategy:
          standard:
            shardingColumn: order_id
            shardingAlgorithmName: t_order_inline
        keyGenerateStrategy:
          column: order_id
          keyGeneratorName: snowflake
    defaultDatabaseStrategy:
      standard:
        shardingColumn: user_id
        shardingAlgorithmName: database_inline
    defaultTableStrategy:
      none:
    bindingTables:
      - t_order
    shardingAlgorithms:
      database_inline:
        type: INLINE
        props:
          algorithm-expression: "readwrite_${(user_id % 3 == 0) ? '77' : ((user_id % 3 == 1) ? '216' : '18')}"
      t_order_inline:
        type: INLINE
        props:
          algorithm-expression: "t_order_${order_id % 2}"
    keyGenerators:
      snowflake:
        type: SNOWFLAKE
        props:
          worker-id: 123
  - !ENCRYPT
    encryptors:
      aes_encryptor:
        type: AES
        props:
          aes-key-value: 123456abc
    tables:
      t_order:
        columns:
          status:
            cipher:
              name: status
              encryptorName: aes_encryptor
props:
  sql-show: true

2.9 ShardingSphereJdbcApplicationTests.java

package org.example;

import org.example.entity.Order;
import org.example.repository.orderRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;

@SpringBootTest
class ShardingSphereJdbcApplicationTests {
    @Autowired
    orderRepository repo;
    @Test
    void contextLoads() {
        repo.findAll().forEach(System.out::println);
    }
    @Test
    void insert(){
        Order newOrder = new Order();
        newOrder.setOrderId(1);
        newOrder.setUserId(10);
        newOrder.setOrderDate(new Date());
        newOrder.setStatus("INSERT");
        repo.save(newOrder);
        System.out.println("Inserted Order: " + newOrder);
    }

    @Test
    void delete(){
        repo.deleteById(1L);
    }
}

2.10 验证结果

2.10.1 分片结果

# 根据 user_id 的值将数据分片到不同的数据源组:
        # 如果 user_id % 3 == 0,则选择 readwrite_77;
        # 如果 user_id % 3 == 1,则选择 readwrite_216;
        # 否则选择 readwrite_18。

# 根据 order_id 的值将数据分片到 t_order_0 或 t_order_1:
        # 如果 order_id % 2 == 0,则选择 t_order_0;
        # 否则选择 t_order_1。

Logic SQL: insert into t_order (order_date,status,user_id,order_id) values (?,?,?,?)
Actual SQL: ds_216_master ::: insert into t_order_1 (order_date,status,user_id,order_id) values (?, ?, ?, ?) ::: [2024-07-26 10:05:21.914, 1jyA91B85/gs5gPPkYF3WA==, 10, 1]
Inserted Order: Order(orderId=1, userId=10, orderDate=Fri Jul 26 10:05:21 CST 2024, status=INSERT)

解释:10%3=1,1%2=1,所以位于主机2(ds_216_master )的数据库, t_order_1表。 

2.10.2 加密结果

Logic SQL: insert into t_order (order_date,status,user_id,order_id) values (?,?,?,?)
Actual SQL: ds_216_master ::: insert into t_order_1 (order_date,status,user_id,order_id) values (?, ?, ?, ?) ::: [2024-07-26 10:05:21.914, 1jyA91B85/gs5gPPkYF3WA==, 10, 1]
Inserted Order: Order(orderId=1, userId=10, orderDate=Fri Jul 26 10:05:21 CST 2024, status=INSERT)

 ​​​​

2.10.3 读写分离结果 

Logic SQL: select o1_0.order_id,o1_0.order_date,o1_0.status,o1_0.user_id from t_order o1_0
Actual SQL: ds_77_slave ::: select o1_0.order_id,o1_0.order_date,o1_0.status AS status,o1_0.user_id from t_order_0 o1_0 UNION ALL select o1_0.order_id,o1_0.order_date,o1_0.status AS status,o1_0.user_id from t_order_1 o1_0
Actual SQL: ds_216_slave ::: select o1_0.order_id,o1_0.order_date,o1_0.status AS status,o1_0.user_id from t_order_0 o1_0 UNION ALL select o1_0.order_id,o1_0.order_date,o1_0.status AS status,o1_0.user_id from t_order_1 o1_0
Actual SQL: ds_18_slave ::: select o1_0.order_id,o1_0.order_date,o1_0.status AS status,o1_0.user_id from t_order_0 o1_0 UNION ALL select o1_0.order_id,o1_0.order_date,o1_0.status AS status,o1_0.user_id from t_order_1 o1_0
Order(orderId=1, userId=10, orderDate=2024-07-26 00:00:00.0, status=INSERT)

3. 总结

        通过Docker-Compose启动3对IP不同的主从数据库,通过JPA和默认的HikariDataSource数据源结合MySQL将ShardingSphere-JDBC整合到SpringBoot框架,实现了读写分离,分库分片,以及加密和加密对特定的字段,仅供学习交流,不具备严谨性。

;