ShardingJdbc-分表;分库;分库分表;读写分离;一主多从+分表;一主多从+分库分表;公共表;数据脱敏;分布式事务

创建项目

一顿下一步,勾选web、lombok等插件

分表

导包

ShardingJdbc

官方最新稳定版4.1.1

<!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/sharding-jdbc-spring-boot-starter -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>4.1.1</version>
</dependency>

Mysql

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.25</version>
</dependency>

Druid

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.6</version>
</dependency>

MyBatisPlus

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.4.3</version>
</dependency>

表结构

注意:之前我这表名叫table_0,结果一直报找不到表,后来发现,不能用关键字

CREATE TABLE `master_db`.`my_table_0` (
  `id` BIGINT (20) NOT NULL,
  `source` VARCHAR (20),
  `remark` VARCHAR (100),
  PRIMARY KEY (`id`)
);

CREATE TABLE `master_db`.`my_table_1` (
  `id` BIGINT (20) NOT NULL,
  `source` VARCHAR (20),
  `remark` VARCHAR (100),
  PRIMARY KEY (`id`)
);

Yml

application-sharding_table.yml

 
server:
  port: 80
spring:
  application:
    name: shardingJdbcDemo
  #shardingsphere相关配置
  shardingsphere:
    datasource:
      names: db   #库名,只是名字
      db: #数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
    sharding: #指定表的分片规则
      tables: # 看到table "s" ,就知道可配置多个
        my_table: # 指定表名,此名必须和model中 @TableName(value = "my_table") 一致
          actualDataNodes: db.my_table_$->{0..1} #创建了两个表,下标0和1,所以此处配置你懂的
          tableStrategy:
            inline: # 指定表的分片策略
              shardingColumn: id #参与分片运算的列名
              algorithmExpression: my_table_$->{id % 2} #分片规则
          keyGenerator: # 指定表的主键生成策略
            type: SNOWFLAKE  #SNOWFLAKE
            column: id  #指定主键
    props:
      sql:
        show: true #输出日志

至此配置完毕,运行效果见下方代码

分库

Yml

application-sharding_db.yml

spring:
  application:
    name: shardingJdbcDemo
  #shardingsphere相关配置
  shardingsphere:
    datasource:
      names: db1,db2   #库名,只是名字
      db1: #从库1
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      db2: #从库2
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
    sharding: #指定表的分片规则
      tables: # 看到table "s" ,就知道可配置多个
        my_table: # 指定表名,此名必须和model中 @TableName(value = "my_table") 一致
          actualDataNodes: db1.my_table
        pub_table:
          actualDataNodes: db2.pub_table
    props:
      sql:
        show: true #输出日志


Java

Table0Controller.java中新增方法

    @GetMapping("saveByShardingDb")
    public void saveByShardingDb(){
        table0Service.save(new Table0(){{setSource("source");setRemark("remark");}});
        pubTableService.save(new PubTable(){{setSource("source");setRemark("remark");}});
    }

测试后可看到数据该去哪去哪了

分库分表

java代码都不改,只改数据库及yml

数据库

master_db、 slave_db1、 slave_db2

库中分别创建my_table_0、my_table_1,结构同 上

Yml

application-sharding_master_slave.yml

spring:
  application:
    name: shardingJdbcDemo
  #shardingsphere相关配置
  shardingsphere:
    datasource:
      names: db0,db1,db2   #库名,只是名字
      db0: #主库
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      db1: #从库1
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      db2: #从库2
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
        #分库分表start
    sharding: #指定表的分片规则
      tables: # 看到table "s" ,就知道可配置多个
        my_table: # 指定表名,此名必须和model中 @TableName(value = "my_table") 一致
          actualDataNodes: db$->{0..2}.my_table_$->{0..1} #创建了两个表,下标0和1,所以此处配置你懂的
          databaseStrategy:
            inline: # 指定表的分片策略
              shardingColumn: id #参与分片运算的列名
              algorithmExpression: db$->{id % 3} #分片算法,分了三个库,所以要%3
          tableStrategy:
            inline: # 指定表的分片策略
              shardingColumn: id #参与分片运算的列名
              algorithmExpression: my_table_$->{id % 2} #分片算法
          keyGenerator: # 指定表的主键生成策略
            type: SNOWFLAKE  #SNOWFLAKE
            column: id  #指定主键
        #分库分表end
    props:
      sql:
        show: true #输出日志

然后继续访问第一步的save&select方法就可看到效果了,配置文件中写的主从库没有实际意义,起错名字了,实际都会往里面写

读写分离

数据库

模拟主从,不做MySql真正的主从同步

上面做表分片时已经创建好了主库master_db 再创建两个库 slave_db1 slave_db2

三个库表结构一致

image-20210602134547999

master_db表中无数据

slave_db1 表数据如下

​ "id" "source" "remark"
​ "1" "source1" "remark1"

slave_db2 表数据如下

​ "id" "source" "remark"
​ "1" "source2" "remark2"

Yml

application-master_slave.yml

spring:
  application:
    name: shardingJdbcDemo
  #shardingsphere相关配置
  shardingsphere:
    datasource:
      names: masterdb,slavedb1,slavedb2   #库名,只是名字
      masterdb: #主库
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      slavedb1: #从库1
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      slavedb2: #从库2
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123

    masterslave:
      name: dbs
      master-data-source-name: masterdb #主库
      slave-data-source-names: slavedb1,slavedb2 #多个从库
      load-balance-algorithm-type: ROUND_ROBIN #RANDOM随机  ROUND_ROBIN 轮训
    props:
      sql:
        show: true #输出日志

这时,访问上面搭好的项目,即可进行读写分离,代码见上方链接

其他

只请求主库

在请求上方添加如下代码即可

    //    HintManager hintManager = HintManager.getInstance();
     //   hintManager.setMasterRouteOnly();
@GetMapping("select")
    public  PageInfo select(int pageNum,int pageSize,Long id){
    //    HintManager hintManager = HintManager.getInstance();
     //   hintManager.setMasterRouteOnly();
        PageHelper.startPage(pageNum,pageSize);
        QueryWrapper<Table0> queryWrapper=new QueryWrapper();
        if(!StringUtils.isEmpty(id)) {
            queryWrapper.eq("id", id);
        }
        List<Table0> list = table0Service.list(queryWrapper);
        PageInfo pageInfo = new PageInfo<>(list);
        return pageInfo;
    }

读写分离判断逻辑代码

大致的流程是shardingJdbc根据sql获得对应的sqlStatement,然后只有SelectStatement的才走从库

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.shardingsphere.masterslave.route.engine.impl;

import lombok.RequiredArgsConstructor;
import org.apache.shardingsphere.api.hint.HintManager;
import org.apache.shardingsphere.core.rule.MasterSlaveRule;
import org.apache.shardingsphere.sql.parser.sql.statement.SQLStatement;
import org.apache.shardingsphere.sql.parser.sql.statement.dml.SelectStatement;

import java.util.ArrayList;

/**
 * Data source router for master-slave.
 */
@RequiredArgsConstructor
public final class MasterSlaveDataSourceRouter {
    
    private final MasterSlaveRule masterSlaveRule;
    
    /**
     * Route.
     * 
     * @param sqlStatement SQL statement
     * @return data source name
     */
    public String route(final SQLStatement sqlStatement) {
        if (isMasterRoute(sqlStatement)) {
            MasterVisitedManager.setMasterVisited();
            return masterSlaveRule.getMasterDataSourceName();
        }
        return masterSlaveRule.getLoadBalanceAlgorithm().getDataSource(
                masterSlaveRule.getName(), masterSlaveRule.getMasterDataSourceName(), new ArrayList<>(masterSlaveRule.getSlaveDataSourceNames()));
    }
    
    private boolean isMasterRoute(final SQLStatement sqlStatement) {
        return containsLockSegment(sqlStatement) || !(sqlStatement instanceof SelectStatement) || MasterVisitedManager.isMasterVisited() || HintManager.isMasterRouteOnly();
    }
    
    private boolean containsLockSegment(final SQLStatement sqlStatement) {
        return sqlStatement instanceof SelectStatement && ((SelecStatement) sqlStatement).getLock().isPresent();
    }
}

一主多从+分表

上面实现了一主多从和表分片,现在把它整合到一起

java代码还是没改,主要改的还是Yml

Yml

application-sharding_db_table_oneMaster_slaves.yml

关键配置见 masterSlaveRule,分片配置未改

#一主多从+表分片
spring:
  application:
    name: shardingJdbcDemo
  #shardingsphere相关配置
  shardingsphere:
    datasource:
      names: db0,db1,db2   #库名,只是名字
      db0: #主库
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      db1: #从库1
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      db2: #从库2
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
    masterSlaveRule: # 这里配置这个规则的话,相当于是全局读写分离配置
      name: ds_rw # 名称,合法的字符串即可,但如果涉及到在读写分离的基础上设置分库分表,则名称需要有意义才可以,另外,虽然目前没有强制要求,但主从库配置需要配置在实际关联的主从库上,如果配置的数据源之间主从是断开的状态,那么可能会发生写入的数据对于只读会话无法读取到的问题
      # 如果一个会话发生了写入并且没有提交(显式打开事务),sharidng sphere在后续的路由中,select都会在主库执行,直到会话提交
      masterDataSourceName: db0 # 主库的DataSource名称
      slaveDataSourceNames: # 从库的DataSource列表,至少需要有一个
        - db1
        - db2
      loadBalanceAlgorithmType: ROUND_ROBIN #从库负载均衡算法类型,可选值:ROUND_ROBIN,RANDOM。若loadBalanceAlgorithmClassName存在则忽略该配置,默认为ROUND_ROBIN
    sharding: #指定表的分片规则
      tables: # 看到table "s" ,就知道可配置多个
        my_table: # 指定表名,此名必须和model中 @TableName(value = "my_table") 一致
          actualDataNodes: db$->{0..2}.my_table_$->{0..1} #创建了两个表,下标0和1,所以此处配置你懂的
          databaseStrategy:
            inline: # 指定表的分片策略
              shardingColumn: id #参与分片运算的列名
              algorithmExpression: db$->{id % 3} #分片算法,分了三个库,所以要%3
          tableStrategy:
            inline: # 指定表的分片策略
              shardingColumn: id #参与分片运算的列名
              algorithmExpression: my_table_$->{id % 2} #分片算法
          keyGenerator: # 指定表的主键生成策略
            type: SNOWFLAKE  #SNOWFLAKE
            column: id  #指定主键
    props:
      sql:
        show: true #输出日志

一主多从+分库分表

上面实现了一主多从和表分片,现在把它整合到一起

java代码还是没改,主要改的还是Yml

创建了两个主库两个从库master_db1、master_db2、slave_db1、slave_db2

下方配置为 master_db1slave_db1为主从关系、master_db2与slave_db2为主从关系

修改点有点多,查看标*****的位置

参考

Yml

application-sharding_db_table_masters_slaves.yml

#一主多从+表分片
spring:
  application:
    name: shardingJdbcDemo
  #shardingsphere相关配置
  shardingsphere:
    datasource:
      names: db0,db1,db2,db3   #库名,只是名字 ******************新增名称db3
      db0: #主库                       
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/master_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      db1: #主库1                         ****************** 添加主库
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/master_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      db2: #从库1
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      db3: #从库2                          ******************添加从库
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
    sharding:
      master-slave-rules: # 新增配置 ******************
        db0: # 此处名字一定要配置和下面 master-data-source-name 名字一致的
          master-data-source-name: db0
          slave-data-source-names: db2
        db1: # 此处名字一定要配置和下面 master-data-source-name 名字一致的
          master-data-source-name: db1
          slave-data-source-names: db3
      tables: # 看到table "s" ,就知道可配置多个
        my_table: # 指定表名,此名必须和model中 @TableName(value = "my_table") 一致
          actualDataNodes: db$->{0..1}.my_table_$->{0..1} #******************每组有一主一从,所以是0..1
          databaseStrategy:
            inline: # 指定表的分片策略
              shardingColumn: id #参与分片运算的列名
              algorithmExpression: db$->{(id / 10).toBigInteger() % 2} # *******不能再用id%2方式了,会导致数据分配不均,所以需要自定义方法
          tableStrategy:
            inline: # 指定表的分片策略
              shardingColumn: id #参与分片运算的列名
              algorithmExpression: my_table_$->{id % 2} #分片算法
          keyGenerator: # 指定表的主键生成策略
            type: SNOWFLAKE  #SNOWFLAKE
            column: id  #指定主键
    props:
      sql:
        show: true #输出日志

公共表&数据脱敏

公共表

项目中会有很多字典表、配置表等等,此类表数据固定、量级不大,所以可以无需采用分库分表,每个库存一份;

当配置公共表后,每次对公共表进行操作,都会同步操作所有库中的公共表

数据库

分别在三个库中新增表pub_table

Java代码

生成pub_table相关的MybatisPlus代码

Yml

application-publicTable_encryptor.yml

spring:
  application:
    name: shardingJdbcDemo
  #shardingsphere相关配置
  shardingsphere:
    datasource:
      names: db0,db1,db2   #库名,只是名字
      db0: #主库
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      db1: #从库1
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
      db2: #从库2
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/slave_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
    sharding:
      default-data-source-name: db #如果有多个库,公共表只有一个库有,那么,就要指定有公共表的库,否则会报找不到表
      default-database-strategy:
        inline:
          sharding-column: id
          algorithm-expression: db$->{id % 3}
      broadcast-tables: pub_table #公共表配置,可以多个
    props:
      sql:
        show: true #输出日志

数据脱敏

参考

spring:
  application:
    name: shardingJdbcDemo
  #shardingsphere相关配置
  shardingsphere:
    datasource:
      names: db   #库名,只是名字
      db: #数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
        username: root
        password: 123
    sharding: #指定表的分片规则
      # 数据脱敏规则配置---start
      encrypt-rule:
        encryptors:
          encryptor_MD5:
            type: MD5
            props:
              md5.key.value: 123456
          encryptor_aes:
            # 加密、解密器的名字,内置的为MD5,aes.
            # 可以自定义,实现
            # org.apache.shardingsphere.encrypt.strategy.spi.Encryptor
            # 或者
            # org.apache.shardingsphere.encrypt.strategy.spi.QueryAssistedEncryptor
            # 这两个接口即可
            type: aes
            props:
              aes.key.value: 123456
        tables:
          # tables
          my_table:
            columns:
              # 逻辑列,就是写SQL里面的列,因为实体类的名字和数据库的加密列一致,所以这里都是name
              source:
                # 原文列
                # plainColumn: password
                # 密文列,用来存储密文数据
                cipherColumn: source
                # 加密器名字
                encryptor: encryptor_MD5
                #  password1:
                # 原文列
                #     plainColumn: password1
                # 密文列,用来存储密文数据
                #   cipherColumn: aes_password
                # 加密器名字
              #   encryptor: encryptor_aes
      # 数据脱敏规则配置---end
      broadcast-tables: pub_table #公共表
      tables: # 看到table "s" ,就知道可配置多个
        my_table: # 指定表名,此名必须和model中 @TableName(value = "my_table") 一致
          actualDataNodes: db.my_table_$->{0..1} #创建了两个表,下标0和1,所以此处配置你懂的
          tableStrategy:
            inline: # 指定表的分片策略
              shardingColumn: id #参与分片运算的列名
              algorithmExpression: my_table_$->{id % 2} #分片算法
          keyGenerator: # 指定表的主键生成策略
            type: SNOWFLAKE  #SNOWFLAKE
            column: id  #指定主键
    props:
      sql:
        show: true #输出日志
      query.with.cipher.column: true #是否使用密文列查询

分布式事务

分库分表后,因为不是一个库了,所以一旦报错,数据无法回滚

Yml

使用上方 application-sharding_db_table_masters_slaves.yml

pom

        <!-- 使用 XA 事务时,需要引入此模块 -->
        <dependency>
            <groupId>io.shardingsphere</groupId>
            <artifactId>sharding-transaction-spring-boot-starter</artifactId>
            <version>3.1.0</version>
            <type>pom</type>
        </dependency>

Java

新增

package com.rollback.shardingjdbcdemo.demo.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
public class TransactionConfiguration {

    @Bean
    public PlatformTransactionManager txManager(final DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public JdbcTemplate jdbcTemplate(final DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

修改save方法

saveByTransaction

package com.rollback.shardingjdbcdemo.demo.sharding.controller;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.rollback.shardingjdbcdemo.demo.sharding.model.Table0;
import com.rollback.shardingjdbcdemo.demo.sharding.service.Table0Service;
import org.apache.shardingsphere.api.hint.HintManager;
import org.apache.shardingsphere.transaction.annotation.ShardingTransactionType;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Random;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author koala
 * @since 2021-05-31
 */
@RestController
@RequestMapping("/table0")
public class Table0Controller {
    @Autowired
    Table0Service table0Service;

    @GetMapping("save")
    public void save(int tag){
            table0Service.save(new Table0(){{setSource("source"+tag);setRemark("remark"+tag);}});
    }
    @Transactional
    @ShardingTransactionType(TransactionType.XA)  // 支持TransactionType.LOCAL, TransactionType.XA, TransactionType.BASE
    @GetMapping("saveByTransaction")
    public void saveByTransaction(int tag){
        for (int i = 0; i < 10; i++) {
            if(i==5){
                List list=null;
                list.add("error");
            }
            table0Service.save(new Table0(){{setSource("source"+tag);setRemark("remark"+tag);}});
        }
    }

    @GetMapping("select")
    public  PageInfo select(int pageNum,int pageSize,Long id){
//        HintManager hintManager = HintManager.getInstance();
//        hintManager.setMasterRouteOnly();
        PageHelper.startPage(pageNum,pageSize);
        QueryWrapper<Table0> queryWrapper=new QueryWrapper();
        if(!StringUtils.isEmpty(id)) {
            queryWrapper.eq("id", id);
        }
        queryWrapper.eq("source","source1");
        List<Table0> list = table0Service.list(queryWrapper);
        PageInfo pageInfo = new PageInfo<>(list);
        return pageInfo;
    }

    @GetMapping("select1")
    public  List<Table0> select1(int pageNum,int pageSize,Long id){
        QueryWrapper<Table0> queryWrapper=new QueryWrapper();
        if(!StringUtils.isEmpty(id)) {
            queryWrapper.eq("id", id);
        }
        //queryWrapper.eq("source","source1");
        List<Table0> list = table0Service.list(queryWrapper);
        return list;
    }
}

验证步骤:

  • 清空数据库

  • 执行saveByTransaction

  • 报错后观察数据库,一条都没有

  • 去掉

    ​ @Transactional
    ​ @ShardingTransactionType(TransactionType.XA) // 支持TransactionType.LOCAL, TransactionType.XA, TransactionType.BASE

    后,会出现保存部分数据的情况

参考官网

代码下载

代码见sharding包下,请求入口见 Table0Controller

1.所写技术都是我工作中用到的
2.所写技术都是自己从项目中提取的
3.所有配置搭建流程都经过2到3遍的测试
4.因都是工作中使用的技术,所以不确定是否有转载的,如果有,请及时通知
原文地址:https://www.cnblogs.com/rb2010/p/14859881.html