1. 版本说明
SpringBoot:2.2.4.RELEASE
MyBatisPlus:3.5.1
ShardingSphere:5.1.1
Seata:1.4.2
<!-- spring-cloud版本 -->
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
<!-- spring-cloud-alibaba版本 -->
<spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
2. 相关依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- mybatis-plus多数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- mybatis-plus代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<!-- ShardingSphere-JDBC -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.1.1</version>
</dependency>
<!-- ShardingSphere事务管理 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-transaction-core</artifactId>
<version>5.1.1</version>
</dependency>
<!-- seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.4.2</version>
</dependency>
</dependencies>
3. 数据源配置
数据源使用配置类进行配置,不需要写在yml文件里。
包含:MyBatisPlus多数据源配置、Seata数据源代理配置、ShardingSphere读写分离和数据分片配置
数据源配置类
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import common.config.datasource.sharding.config.ShardingSphereAlgorithmConstants;
import common.config.datasource.sharding.table.SysLogShardingConfig;
import common.config.mybatisplus.CustomMetaObjectHandler;
import common.config.mybatisplus.MyBatisPlusConfig;
import common.model.enums.EnvironmentEnum;
import io.seata.rm.datasource.DataSourceProxy;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory;
import org.apache.shardingsphere.infra.config.RuleConfiguration;
import org.apache.shardingsphere.infra.config.mode.ModeConfiguration;
import org.apache.shardingsphere.readwritesplitting.api.ReadwriteSplittingRuleConfiguration;
import org.apache.shardingsphere.readwritesplitting.api.rule.ReadwriteSplittingDataSourceRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.strategy.sharding.StandardShardingStrategyConfiguration;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;
/**
* 数据源配置<p>
* 可切换的数据源有:<p>
* 1. ShardingSphere数据源,包含master、slave01、slave02上的satisfactory数据库,读写分离、分库分表:@DS(DataSourceConstants.SHARDING_DATASOURCE)<p>
* {@link DataSourceConstants#SHARDING_DATASOURCE}<p>
* 2. master数据源,仅包含master主机上的satisfactory数据库,无数据分片和读写分离:@DS(DataSourceConstants.MASTER_DATASOURCE)<p>
* {@link DataSourceConstants#MASTER_DATASOURCE}<p>
* 3. 慢查询日志数据源,仅包含master主机上的mysql数据库,无数据分片和读写分离:@DS(DataSourceConstants.MYSQL_DATASOURCE)<p>
* {@link DataSourceConstants#MYSQL_DATASOURCE}<p>
* 4. information_schema数据源,仅包含master主机上的information_schema数据库,无数据分片和读写分离:@DS(DataSourceConstants.INFORMATION_SCHEMA_DATASOURCE)<p>
* {@link DataSourceConstants#INFORMATION_SCHEMA_DATASOURCE}<p>
*
* @since 2023-7-28 下午 9:26
*/
@Configuration
@Slf4j
public class DataSourceConfig {
/**
* 动态数据源
*/
private static final String DYNAMIC_DATASOURCE = "dynamic";
/**
* 慢查询日志数据源(原始)
*/
private static final String ORIGINAL_MYSQL_DATASOURCE = "originalMysql";
/**
* information_schema数据源(原始)
*/
private static final String ORIGINAL_INFORMATION_SCHEMA_DATASOURCE = "originalInformationSchema";
/**
* 真实数据节点:master
*/
private static final String MASTER_RAW_DATASOURCE = "master";
/**
* 真实数据节点:slave01
*/
private static final String SLAVE01_RAW_DATASOURCE = "slave01";
/**
* 真实数据节点:slave02
*/
private static final String SLAVE02_RAW_DATASOURCE = "slave02";
/**
* SqlSessionFactory
*/
private static final String DYNAMIC_SQL_SESSION_FACTORY = "dynamicSqlSessionFactory";
@Value("${spring.application.name}")
private String appName;
@Resource
private Environment environment;
@Resource
private CustomMetaObjectHandler metaObjectHandler;
/**
* 配置见{@link MyBatisPlusConfig#mybatisPlusInterceptor()}
*/
@Resource
private MybatisPlusInterceptor mybatisPlusInterceptor;
/**
* 非读写分离数据源:mysql
*/
@Bean(name = ORIGINAL_MYSQL_DATASOURCE)
public DataSource mysql() {
return buildDataSource("3307", "mysql");
}
/**
* mysql数据源代理
*/
@Bean(DataSourceConstants.MYSQL_DATASOURCE)
public DataSourceProxy dataSourceProxyMysql(@Qualifier(ORIGINAL_MYSQL_DATASOURCE) DataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
/**
* 非读写分离数据源:information_schema
*/
@Bean(name = ORIGINAL_INFORMATION_SCHEMA_DATASOURCE)
public DataSource informationSchema() {
return buildDataSource("3307", "mysql");
}
/**
* information_schema数据源代理
*/
@Bean(DataSourceConstants.INFORMATION_SCHEMA_DATASOURCE)
public DataSourceProxy dataSourceProxyInformationSchema(@Qualifier(ORIGINAL_INFORMATION_SCHEMA_DATASOURCE) DataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
/**
* master数据源代理
*/
@Bean(DataSourceConstants.MASTER_DATASOURCE)
public DataSourceProxy dataSourceProxyMaster(@Qualifier(MASTER_RAW_DATASOURCE) DataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
/**
* 真实数据节点:master
*/
@Bean(name = MASTER_RAW_DATASOURCE)
public DataSource master() {
return buildDataSource("3307", "satisfactory");
}
/**
* 真实数据节点:slave01
*/
@Bean(name = SLAVE01_RAW_DATASOURCE)
public DataSource slave01() {
return buildDataSource("3308", "satisfactory");
}
/**
* 真实数据节点:slave02
*/
@Bean(name = SLAVE02_RAW_DATASOURCE)
public DataSource slave02() {
return buildDataSource("3309", "satisfactory");
}
private DataSource buildDataSource(String port, String databaseName) {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://localhost:" + port + "/" + databaseName + "?rewriteBatchedStatements=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai");
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUsername("root");
druidDataSource.setPassword("123456");
return druidDataSource;
}
/**
* ShardingSphere数据源代理
*/
@Bean(DataSourceConstants.SHARDING_DATASOURCE)
// 重要:必须在分片配置类加载后才能加载,否则无法获取分片算法
@DependsOn({"sysLogShardingConfig"})
public DataSourceProxy dataSource(@Qualifier(MASTER_RAW_DATASOURCE) DataSource master, @Qualifier(SLAVE01_RAW_DATASOURCE) DataSource slave01, @Qualifier(SLAVE02_RAW_DATASOURCE) DataSource slave02) throws SQLException {
log.info("init sharding datasource");
// 运行模式类型,可选配置:Memory、Standalone、Cluster,默认为Memory
ModeConfiguration modeConfiguration = new ModeConfiguration("Memory", null, false);
// 数据源配置
Map<String, DataSource> dataSourceMap = new HashMap<>();
dataSourceMap.put(MASTER_RAW_DATASOURCE, master);
dataSourceMap.put(SLAVE01_RAW_DATASOURCE, slave01);
dataSourceMap.put(SLAVE02_RAW_DATASOURCE, slave02);
// 规则配置,可选配置:数据分片、读写分离、数据加密、影子库、SQL解析、混合规则
Set<RuleConfiguration> configs = new HashSet<>();
// 读写分离配置
configs.add(getReadwriteSplittingRuleConfiguration());
// 数据分片配置
ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();
// 初始化所有算法
shardingRuleConfiguration.getShardingAlgorithms().putAll(ShardingSphereAlgorithmConstants.SHARDING_ALGORITHMS);
// 需要分片的表(需要分片的表都写在这里,目前只有一张表)
shardingRuleConfiguration.getTables().add(getSysLogShardingConfiguration());
configs.add(shardingRuleConfiguration);
// 其他配置
Properties properties = new Properties();
// 在非生产环境打印日志
if (environment.acceptsProfiles(Profiles.of(EnvironmentEnum.PRODUCT.getName()))) {
properties.setProperty("sql-show", Boolean.FALSE.toString());
} else {
properties.setProperty("sql-show", Boolean.TRUE.toString());
}
// 创建数据源代理
return new DataSourceProxy(ShardingSphereDataSourceFactory.createDataSource(modeConfiguration, dataSourceMap, configs, properties));
}
/**
* 动态数据源
*/
@Bean(DYNAMIC_DATASOURCE)
public DataSource dynamicDataSource(@Qualifier(DataSourceConstants.SHARDING_DATASOURCE) DataSourceProxy shardingDatasource, @Qualifier(DataSourceConstants.MASTER_DATASOURCE) DataSourceProxy masterDatasource, @Qualifier(DataSourceConstants.MYSQL_DATASOURCE) DataSourceProxy mysqlDatasource, @Qualifier(DataSourceConstants.INFORMATION_SCHEMA_DATASOURCE) DataSourceProxy informationSchemaDatasource) {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
dynamicRoutingDataSource.addDataSource(DataSourceConstants.SHARDING_DATASOURCE, shardingDatasource);
dynamicRoutingDataSource.addDataSource(DataSourceConstants.MASTER_DATASOURCE, masterDatasource);
dynamicRoutingDataSource.addDataSource(DataSourceConstants.MYSQL_DATASOURCE, mysqlDatasource);
dynamicRoutingDataSource.addDataSource(DataSourceConstants.INFORMATION_SCHEMA_DATASOURCE, informationSchemaDatasource);
dynamicRoutingDataSource.setPrimary(DataSourceConstants.SHARDING_DATASOURCE);
dynamicRoutingDataSource.setSeata(true);
return dynamicRoutingDataSource;
}
/**
* 创建事务管理器,解决@Transactional注解失效的问题
*/
@Bean
public DataSourceTransactionManager shardingTransactionManger(@Qualifier(DYNAMIC_DATASOURCE) DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* 创建SqlSessionFactory
*/
@Bean(DYNAMIC_SQL_SESSION_FACTORY)
public SqlSessionFactory sqlSessionFactory(@Qualifier(DYNAMIC_DATASOURCE) DataSource dataSource) throws Exception {
// 这里用 MybatisSqlSessionFactoryBean 代替了 SqlSessionFactoryBean,否则 MyBatisPlus 不会生效
MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
mybatisSqlSessionFactoryBean.setDataSource(dataSource);
mybatisSqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:" + "{id:common|" + appName + "}/mapper/xml/*.xml"));
mybatisSqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
// MyBatis-Plus 配置
MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
// MyBatis-Plus的分页、乐观锁等功能是通过MybatisPlusInterceptor插入的
mybatisConfiguration.addInterceptor(mybatisPlusInterceptor);
// 在非生产环境打印日志
if (!environment.acceptsProfiles(Profiles.of(EnvironmentEnum.PRODUCT.getName()))) {
mybatisConfiguration.setLogImpl(StdOutImpl.class);
}
// 返回为null的字段
mybatisConfiguration.setCallSettersOnNulls(true);
mybatisSqlSessionFactoryBean.setConfiguration(mybatisConfiguration);
GlobalConfig globalConfig = new GlobalConfig();
// 字段自动填充
globalConfig.setMetaObjectHandler(metaObjectHandler);
GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
// 主键策略
dbConfig.setIdType(IdType.AUTO);
// 查询策略
dbConfig.setWhereStrategy(FieldStrategy.DEFAULT);
// 插入策略
dbConfig.setInsertStrategy(FieldStrategy.DEFAULT);
// 更新策略(IGNORED表示null值也会更新)
dbConfig.setUpdateStrategy(FieldStrategy.IGNORED);
globalConfig.setDbConfig(dbConfig);
mybatisSqlSessionFactoryBean.setGlobalConfig(globalConfig);
return mybatisSqlSessionFactoryBean.getObject();
}
/**
* 创建SqlSessionTemplate
*/
@Bean
public SqlSessionTemplate sqlSessionTemplate(@Qualifier(DYNAMIC_SQL_SESSION_FACTORY) SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
/**
* 读写分离配置
*/
private ReadwriteSplittingRuleConfiguration getReadwriteSplittingRuleConfiguration() {
// 负载均衡算法配置
Properties dataSourceProperties = new Properties();
// 写数据源名称
dataSourceProperties.setProperty("write-data-source-name", MASTER_RAW_DATASOURCE);
// 读数据源名称,多个从数据源用逗号分隔
dataSourceProperties.setProperty("read-data-source-names", SLAVE01_RAW_DATASOURCE + "," + SLAVE02_RAW_DATASOURCE);
// 自定义的读写分离数据源名称:shardingDatasource
// 读写分离类型,如: Static,Dynamic
ReadwriteSplittingDataSourceRuleConfiguration dataSourceRuleConfiguration = new ReadwriteSplittingDataSourceRuleConfiguration(DataSourceConstants.SHARDING_DATASOURCE, "Static", dataSourceProperties, ShardingSphereAlgorithmConstants.READWRITE_ALG_ROUND);
return new ReadwriteSplittingRuleConfiguration(Collections.singleton(dataSourceRuleConfiguration), ShardingSphereAlgorithmConstants.READWRITE_ALGORITHMS);
}
/**
* sys_log表分片配置
* <p>
* 其中主键生成策略使用了MyBatis-Plus提供的主键策略{@link IdType#ASSIGN_ID}
*/
private ShardingTableRuleConfiguration getSysLogShardingConfiguration() {
// 指定逻辑表名和真实数据节点
ShardingTableRuleConfiguration tableRuleConfiguration = new ShardingTableRuleConfiguration(SysLogShardingConfig.LOGIC_TABLE_NAME, SysLogShardingConfig.ACTUAL_DATA_NODES);
// 分库策略
// tableRuleConfiguration.setDatabaseShardingStrategy(new StandardShardingStrategyConfiguration("user_id", ShardingSphereAlgorithmConstants.SHARDING_DATABASE_INLINE));
// 分表策略
tableRuleConfiguration.setTableShardingStrategy(new StandardShardingStrategyConfiguration(DataSourceConstants.ID_FIELD_NAME_IN_DATABASE, SysLogShardingConfig.SYS_LOG_TABLE_ALG));
// 主键生成策略(未使用,而是使用了MyBatis-Plus提供的主键策略IdType.ASSIGN_ID)
// Properties snowflakeProp = new Properties();
// snowflakeProp.setProperty("worker-id", "1");
// shardingRuleConfiguration.getKeyGenerators().put("snowflake", new ShardingSphereAlgorithmConfiguration("SNOWFLAKE", snowflakeProp));
// tableRuleConfiguration.setKeyGenerateStrategy(new KeyGenerateStrategyConfiguration("id", "snowflake"));
return tableRuleConfiguration;
}
}
4. Seata配置文件
registry.conf(resources下)
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "eureka"
nacos {
serverAddr = "localhost"
namespace = ""
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "seata"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
password = ""
cluster = "default"
timeout = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
file.conf(resources下)
transport {
# tcp udt unix-domain-socket
type = "TCP"
# NIO NATIVE
server = "NIO"
# enable heartbeat
heartbeat = true
# the client batch send request enable
enableClientBatchSendRequest = true
# thread factory for netty
threadFactory {
bossThreadPrefix = "NettyBoss"
workerThreadPrefix = "NettyServerNIOWorker"
serverExecutorThread-prefix = "NettyServerBizHandler"
shareBossWorker = false
clientSelectorThreadPrefix = "NettyClientSelector"
clientSelectorThreadSize = 1
clientWorkerThreadPrefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
bossThreadSize = 1
# auto default pin or 8
workerThreadSize = "default"
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
# transaction service group mapping
vgroupMapping.satisfactory_seata_group = "seata"
# only support when registry.type=file, please don't set multiple addresses
# 此配置作用参考:https://blog.csdn.net/weixin_39800144/article/details/100726116
default.grouplist = "127.0.0.1:8091"
# degrade, current not support
enableDegrade = false
# disable seata
disableGlobalTransaction = false
}
client {
rm {
asyncCommitBufferLimit = 10000
lock {
retryInterval = 10
retryTimes = 30
retryPolicyBranchRollbackOnConflict = true
}
reportRetryCount = 5
tableMetaCheckEnable = false
reportSuccessEnable = false
}
tm {
commitRetryCount = 5
rollbackRetryCount = 5
}
undo {
dataValidation = true
logSerialization = "jackson"
logTable = "seata_undo_log"
}
log {
exceptionRate = 100
}
}
seata.conf(resources下)
这个是ShardingSphere整合Seata需要的配置,单独使用Seata时不需要
client {
## 应用唯一主键
application.id = gateway
## 所属事务组(与Seata中的配置一致)
transaction.service.group = satisfactory_seata_group
}
5. ShardingSphere算法配置
包含:读写分离的负载均衡算法、数据分片算法
定义算法集合的类,容纳了ShardingSphere需要的所有算法
import org.apache.shardingsphere.infra.config.algorithm.ShardingSphereAlgorithmConfiguration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
/**
* ShardingSphere算法配置
*
* @since 2023-7-29 下午 7:07
*/
public class ShardingSphereAlgorithmConstants {
/**
* 读写分离的负载均衡算法
*/
public static final Map<String, ShardingSphereAlgorithmConfiguration> READWRITE_ALGORITHMS = new HashMap<>();
/**
* 算法名称:读写分离算法(ROUND_ROBIN)
*/
public static final String READWRITE_ALG_ROUND = "alg_round";
/**
* 算法名称:读写分离算法(RANDOM)
*/
public static final String READWRITE_ALG_RANDOM = "alg_random";
/**
* 算法名称:读写分离算法(WEIGHT)
*/
public static final String READWRITE_ALG_WEIGHT = "alg_weight";
/**
* 数据分片算法
*/
public static final Map<String, ShardingSphereAlgorithmConfiguration> SHARDING_ALGORITHMS = new LinkedHashMap<>();
static {
// 负载均衡算法
READWRITE_ALGORITHMS.put(READWRITE_ALG_ROUND, new ShardingSphereAlgorithmConfiguration("ROUND_ROBIN", null));
READWRITE_ALGORITHMS.put(READWRITE_ALG_RANDOM, new ShardingSphereAlgorithmConfiguration("RANDOM", null));
Properties properties = new Properties();
properties.setProperty("slave01", "1");
properties.setProperty("slave02", "2");
READWRITE_ALGORITHMS.put(READWRITE_ALG_WEIGHT, new ShardingSphereAlgorithmConfiguration("WEIGHT", properties));
}
}
每个表的数据分片算法(这里只有一张表sys_log需要分片)
import common.config.datasource.DataSourceConstants;
import common.config.datasource.sharding.config.ShardingSphereAlgorithmConstants;
import common.config.datasource.sharding.config.ShardingSphereAlgorithmUtil;
import org.springframework.stereotype.Component;
/**
* sys_log表分片配置
*
* @since 2023-8-14 上午 11:47
*/
@Component("sysLogShardingConfig")
public class SysLogShardingConfig {
/**
* 逻辑表名称
*/
public static final String LOGIC_TABLE_NAME = "sys_log";
/**
* 真实数据节点
*/
public static final String ACTUAL_DATA_NODES = DataSourceConstants.SHARDING_DATASOURCE + ".sys_log_$->{1..2}";
/**
* 算法名称
*/
public static final String SYS_LOG_TABLE_ALG = "sys_log_table_alg";
public static final String SYS_LOG_TABLE_ALG_2 = "sys_log_table_alg_2";
public static final String SYS_LOG_TABLE_ALG_3 = "sys_log_table_alg_3";
static {
ShardingSphereAlgorithmConstants.SHARDING_ALGORITHMS.put(SYS_LOG_TABLE_ALG, ShardingSphereAlgorithmUtil.buildInlineAlgorithmConfiguration("sys_log_$->{id % 2 + 1}"));
ShardingSphereAlgorithmConstants.SHARDING_ALGORITHMS.put(SYS_LOG_TABLE_ALG_2, ShardingSphereAlgorithmUtil.buildHashModAlgorithmConfiguration("2"));
ShardingSphereAlgorithmConstants.SHARDING_ALGORITHMS.put(SYS_LOG_TABLE_ALG_3, ShardingSphereAlgorithmUtil.buildInlineAlgorithmConfiguration("sys_log_$->{execution_time > 1000 ? 2 : 1}"));
}
}
6. ShardingSphere分布式事务切面
需要显式设置分布式事务模式,也就是仅仅在注解里写TransactionType.BASE是不生效的,需要在方法执行前,调用TransactionTypeHolder.set(TransactionType.BASE),因此使用AOP拦截所有加了 @ShardingSphereTransactionType 注解的方法。
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
import org.apache.shardingsphere.transaction.core.TransactionTypeHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 设置ShardingSphere分布式事务类型
*
* @since 2023-8-10 上午 2:08
*/
@Component
@Aspect
// 如果一个方法被多个aop增强, 可以用@Order来控制增强顺序, 值越小优先级越高
@Order(-1)
@Slf4j
public class ShardingTransactionAspect {
@Pointcut("@annotation(org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType)")
public void pointCut() {
}
@Before("pointCut()")
public void doBefore(JoinPoint point) {
// 优先获取方法上的注解,如果方法上没有注解,则获取类上的注解
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
ShardingSphereTransactionType transactionType = method.getAnnotation(ShardingSphereTransactionType.class);
if (transactionType == null) {
transactionType = point.getTarget().getClass().getAnnotation(ShardingSphereTransactionType.class);
}
if (transactionType == null) {
log.warn("无法获取ShardingSphere分布式事务类型");
return;
}
TransactionTypeHolder.set(transactionType.value());
log.info("设置ShardingSphere分布式事务类型为:" + transactionType.value().toString());
}
@After("pointCut()")
public void doAfter() {
TransactionTypeHolder.clear();
}
}
7. 解决ShardingSphere的兼容性问题
项目未使用shardingsphere-transaction-base-seata-at的依赖,原因是该包里的SeataTransactionHolder为package,外部无法调用其方法,故不引入此依赖,改为引入shardingsphere-transaction-core,但需要在项目java目录下新建org/apache/shardingsphere/transaction/base/seata/at包,放入以下三个类(这三个类来源于shardingsphere-transaction-base-seata-at依赖):
SeataATShardingSphereTransactionManager.java
修改了源码中isInTransaction()方法的一处判断
import com.google.common.base.Preconditions;
import io.seata.config.FileConfiguration;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.core.rpc.netty.RmNettyRemotingClient;
import io.seata.core.rpc.netty.TmNettyRemotingClient;
import io.seata.rm.RMClient;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.tm.TMClient;
import io.seata.tm.api.GlobalTransaction;
import io.seata.tm.api.GlobalTransactionContext;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.infra.database.type.DatabaseType;
import org.apache.shardingsphere.transaction.core.ResourceDataSource;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.apache.shardingsphere.transaction.rule.TransactionRule;
import org.apache.shardingsphere.transaction.spi.ShardingSphereTransactionManager;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Seata AT transaction manager.
*/
@Slf4j
public final class SeataATShardingSphereTransactionManager implements ShardingSphereTransactionManager {
private final Map<String, DataSource> dataSourceMap = new HashMap<>();
private final String applicationId;
private final String transactionServiceGroup;
private final boolean enableSeataAT;
private final int globalTXTimeout;
public SeataATShardingSphereTransactionManager() {
FileConfiguration config = new FileConfiguration("seata.conf");
enableSeataAT = config.getBoolean("sharding.transaction.seata.at.enable", true);
applicationId = config.getConfig("client.application.id");
transactionServiceGroup = config.getConfig("client.transaction.service.group", "default");
globalTXTimeout = config.getInt("sharding.transaction.seata.tx.timeout", 60);
}
@Override
public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources, final TransactionRule transactionRule) {
if (enableSeataAT) {
initSeataRpcClient();
resourceDataSources.forEach(each -> dataSourceMap.put(each.getOriginalName(), new DataSourceProxy(each.getDataSource())));
}
}
private void initSeataRpcClient() {
Preconditions.checkNotNull(applicationId, "please config application id within seata.conf file.");
TMClient.init(applicationId, transactionServiceGroup);
RMClient.init(applicationId, transactionServiceGroup);
}
@Override
public TransactionType getTransactionType() {
return TransactionType.BASE;
}
@Override
public boolean isInTransaction() {
Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
// 这里替换了源码中的判断方式,原方式会导致在commit和rollback时SeataTransactionHolder.get()返回null,报空指针
// return null != RootContext.getXID();
return null != SeataTransactionHolder.get();
}
@Override
public Connection getConnection(final String dataSourceName) throws SQLException {
Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
return dataSourceMap.get(dataSourceName).getConnection();
}
@Override
public void begin() {
begin(globalTXTimeout);
}
@Override
@SneakyThrows(TransactionException.class)
public void begin(final int timeout) {
if (timeout < 0) {
throw new TransactionException("timeout should more than 0s");
}
Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
GlobalTransaction globalTransaction = GlobalTransactionContext.getCurrentOrCreate();
globalTransaction.begin(timeout * 1000);
SeataTransactionHolder.set(globalTransaction);
}
@Override
@SneakyThrows(TransactionException.class)
public void commit(final boolean rollbackOnly) {
Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
try {
SeataTransactionHolder.get().commit();
} finally {
SeataTransactionHolder.clear();
RootContext.unbind();
}
}
@Override
@SneakyThrows(TransactionException.class)
public void rollback() {
Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
try {
SeataTransactionHolder.get().rollback();
} finally {
SeataTransactionHolder.clear();
RootContext.unbind();
}
}
@Override
public void close() {
dataSourceMap.clear();
SeataTransactionHolder.clear();
RmNettyRemotingClient.getInstance().destroy();
TmNettyRemotingClient.getInstance().destroy();
}
}
SeataTransactionHolder.java
import io.seata.tm.api.GlobalTransaction;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* Seata transaction holder.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SeataTransactionHolder {
private static final ThreadLocal<GlobalTransaction> CONTEXT = new ThreadLocal<>();
/**
* Set seata global transaction.
*
* @param transaction global transaction context
*/
public static void set(final GlobalTransaction transaction) {
CONTEXT.set(transaction);
}
/**
* Get seata global transaction.
*
* @return global transaction
*/
public static GlobalTransaction get() {
return CONTEXT.get();
}
/**
* Clear global transaction.
*/
public static void clear() {
CONTEXT.remove();
}
}
TransactionalSQLExecutionHook.java
import io.seata.core.context.RootContext;
import org.apache.shardingsphere.infra.database.metadata.DataSourceMetaData;
import org.apache.shardingsphere.infra.executor.kernel.model.ExecutorDataMap;
import org.apache.shardingsphere.infra.executor.sql.hook.SQLExecutionHook;
import java.util.List;
import java.util.Map;
/**
* Seata transactional SQL execution hook.
*/
public final class TransactionalSQLExecutionHook implements SQLExecutionHook {
private static final String SEATA_TX_XID = "SEATA_TX_XID";
private boolean seataBranch;
@Override
public void start(final String dataSourceName, final String sql, final List<Object> parameters,
final DataSourceMetaData dataSourceMetaData, final boolean isTrunkThread, final Map<String, Object> shardingExecuteDataMap) {
if (isTrunkThread) {
if (RootContext.inGlobalTransaction()) {
ExecutorDataMap.getValue().put(SEATA_TX_XID, RootContext.getXID());
}
} else if (!RootContext.inGlobalTransaction() && shardingExecuteDataMap.containsKey(SEATA_TX_XID)) {
RootContext.bind((String) shardingExecuteDataMap.get(SEATA_TX_XID));
seataBranch = true;
}
}
@Override
public void finishSuccess() {
if (seataBranch) {
RootContext.unbind();
}
}
@Override
public void finishFailure(final Exception cause) {
if (seataBranch) {
RootContext.unbind();
}
}
}
由于ShardingSphere是通过SPI方式加载的该类,故在子系统的resources目录下新建META-INF/services目录,新建两个文件:
文件1:
名为:org.apache.shardingsphere.infra.executor.sql.hook.SQLExecutionHook
内容为:org.apache.shardingsphere.transaction.base.seata.at.TransactionalSQLExecutionHook
文件2:
名为:org.apache.shardingsphere.transaction.spi.ShardingSphereTransactionManager
内容为:org.apache.shardingsphere.transaction.base.seata.at.SeataATShardingSphereTransactionManager
8. 数据分片表的主键生成策略
可以使用ShardingSphere内置的雪花算法,也可以使用MyBatisPlus提供的主键生成策略,这里用的后者。
在Java实体类中,可以这样定义主键字段:
/**
* 主键
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
其中 IdType.ASSIGN_ID 是分配ID (主键类型为number或string),只有当插入对象id为空才自动填充,默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
9. 如何使用事务控制
本地事务:@Transactional(rollbackFor = Exception.class) 即可
Seata全局事务:@Transactional(rollbackFor = Exception.class) 和 @ShardingSphereTransactionType(TransactionType.BASE) 同时使用
被feign调用的方法也需要加上相同的注解,其中ShardingSphereTransactionType的注解作用就是设置当前事务类型为BASE,这样shardingConnection里的transactionmanager就会放入SeataATShardingSphereTransactionManager,这一步会使本地事务转为分布式事务,并与seata通信。
10. 如何切换数据源
方法一:使用 @DS 切换数据源
@DS 可以注解在方法上或类上,同时存在就近原则,方法上的注解优先于类上的注解。
/**
* serice实现类
*/
@Service
@DS("datasource1")
public class MysqlSlowLogServiceImpl extends ServiceImpl<MysqlSlowLogMapper, MySqlSlowLog> implements MysqlSlowLogService {
@Resource
private MysqlSlowLogMapper mapper;
@Override
public Page<MySqlSlowLog> pageSlowLog(PaginationRequest<SearchSlowLogRequest, MySqlSlowLog> paginationRequest) {
return page(paginationRequest.buildPage(), paginationRequest.buildQueryWrapper());
}
}
方法二:用 DynamicDataSourceContextHolder.push() 手动切换数据源
@Override
@DS("datasource1")
public void getCopyDataInfo(String targetTableName, Long serverId, String dbName, Long taskSetId) {
GetCopyDataInfoVO info = getCopyDataInfoOne(targetTableName, serverId, dbName, taskSetId);
// 手动切换数据源
DynamicDataSourceContextHolder.push("datasource2");
saveCopyDataInfo(info.getSyncInfo(), info.getTaskSetId(), info.getSrTableInfo(), info.getTgtTableInfo(), info.getSrColInfoList(), info.getTgtColInfoList());
}
11. 代码里涉及到的其他类
CustomMetaObjectHandler.java
MyBatisPlus自定义字段自动填充处理类
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import common.config.datasource.DataSourceConstants;
import common.config.jwt.JwtUtil;
import common.model.enums.EnvironmentEnum;
import common.util.Constants;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Objects;
import java.util.function.Supplier;
/**
* MyBatisPlus自定义字段自动填充处理类 - 实体类中使用 @TableField 注解<br/>
* 3.3.0版本以后推荐使用strictInsertFill()和strictUpdateFill()进行填充,会判断字段是否加了自动填充的注解<br/>
* setFieldValByName()和fillStrategy()不会判断字段是否加了自动填充的注解,需要自己判断<br/>
* fillStrategy()不能填充乐观锁字段,因为默认字段有值就不进行填充<br/>
*/
@Component
@Slf4j
public class CustomMetaObjectHandler implements MetaObjectHandler {
private static final String INSERT_DESC = "插入数据";
private static final String UPDATE_DESC = "更新数据";
@Value("${spring.application.name}")
private String appName;
@Resource
private HttpServletRequest request;
@Resource
private Environment environment;
/**
* 插入数据时
*/
@Override
public void insertFill(MetaObject metaObject) {
Date current = new Date();
// 创建时间
strictInsertFill(metaObject, DataSourceConstants.CREATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, Date.class, current);
printLog(INSERT_DESC, DataSourceConstants.CREATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, DateUtil.format(current, DatePattern.NORM_DATETIME_MS_PATTERN));
// 更新时间
strictInsertFill(metaObject, DataSourceConstants.UPDATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, Date.class, current);
printLog(INSERT_DESC, DataSourceConstants.UPDATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, DateUtil.format(current, DatePattern.NORM_DATETIME_MS_PATTERN));
// 操作人
switch (appName) {
case Constants.SERVICE_NAME_GATEWAY:
case Constants.SERVICE_NAME_SYSTEM:
case Constants.SERVICE_NAME_GAME:
String operator = JwtUtil.getUsernameFromRequest(request);
strictInsertFill(metaObject, DataSourceConstants.OPERATOR_FIELD_NAME_IN_JAVA_ENTITY, String.class, operator);
printLog(INSERT_DESC, DataSourceConstants.OPERATOR_FIELD_NAME_IN_JAVA_ENTITY, operator);
break;
case Constants.SERVICE_NAME_LOG:
default:
break;
}
}
/**
* 更新数据时
*/
@Override
public void updateFill(MetaObject metaObject) {
Date current = new Date();
// 更新时间
strictUpdateFill(metaObject, DataSourceConstants.UPDATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, Date.class, current);
printLog(UPDATE_DESC, DataSourceConstants.UPDATE_TIME_FIELD_NAME_IN_JAVA_ENTITY, DateUtil.format(current, DatePattern.NORM_DATETIME_MS_PATTERN));
// 操作人
switch (appName) {
case Constants.SERVICE_NAME_GATEWAY:
case Constants.SERVICE_NAME_SYSTEM:
case Constants.SERVICE_NAME_GAME:
String operator = JwtUtil.getUsernameFromRequest(request);
strictUpdateFill(metaObject, DataSourceConstants.OPERATOR_FIELD_NAME_IN_JAVA_ENTITY, String.class, operator);
printLog(UPDATE_DESC, DataSourceConstants.OPERATOR_FIELD_NAME_IN_JAVA_ENTITY, operator);
break;
case Constants.SERVICE_NAME_LOG:
default:
break;
}
}
/**
* 自定义严格模式填充策略:<br/>
* ① 覆盖原值<br/>
* ② 如果提供的值为null则不填充<br/>
*/
@Override
public MetaObjectHandler strictFillStrategy(MetaObject metaObject, String fieldName, Supplier<?> fieldVal) {
Object obj = fieldVal.get();
if (Objects.nonNull(obj)) {
metaObject.setValue(fieldName, obj);
}
return this;
}
/**
* 打印日志
*
* @param fillType 插入数据/更新数据
* @param fieldName 字段名
* @param fieldVal 字段值
*/
private void printLog(String fillType, String fieldName, Object fieldVal) {
// 在非生产环境打印日志
if (!environment.acceptsProfiles(Profiles.of(EnvironmentEnum.PRODUCT.getName()))) {
log.info("MyBatisPlus自动填充【{}】 - {}:{}", fillType, fieldName, fieldVal);
}
}
}
MyBatisPlusConfig.java
MyBatisPlus插件配置
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatisPlus插件配置,注意:
* 使用多个功能需要注意顺序关系,建议使用如下顺序
* 多租户,动态表名
* 分页,乐观锁
* sql性能规范,防止全表更新与删除
* 总结: 对sql进行单次改造的优先放入,不对sql进行改造的最后放入
*/
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防止全表更新与删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
DataSourceConstants.java
数据源相关的常量
import common.model.po.SysUser;
import common.util.ClassUtil;
import io.seata.common.exception.ShouldNeverHappenException;
import org.apache.shardingsphere.infra.database.DefaultSchema;
/**
* 数据源相关的常量<p>
* 其中数据源名称必须小写
*
* @since 2023-7-29 下午 7:07
*/
public class DataSourceConstants {
public static final String ID_FIELD_NAME_IN_DATABASE = "id";
public static final String OPERATOR_FIELD_NAME_IN_DATABASE = "operator";
public static final String CREATE_TIME_FIELD_NAME_IN_DATABASE = "create_time";
public static final String UPDATE_TIME_FIELD_NAME_IN_DATABASE = "update_time";
public static final String ID_FIELD_NAME_IN_JAVA_ENTITY = ClassUtil.getFieldName(SysUser::getId);
public static final String OPERATOR_FIELD_NAME_IN_JAVA_ENTITY = ClassUtil.getFieldName(SysUser::getOperator);
public static final String CREATE_TIME_FIELD_NAME_IN_JAVA_ENTITY = ClassUtil.getFieldName(SysUser::getCreateTime);
public static final String UPDATE_TIME_FIELD_NAME_IN_JAVA_ENTITY = ClassUtil.getFieldName(SysUser::getUpdateTime);
/**
* ShardingSphere数据源:读写分离 + 分库分表
* 数据源名称必须设置为{@link DefaultSchema#LOGIC_NAME},否则插入数据时报错{@link ShouldNeverHappenException}
*/
public static final String SHARDING_DATASOURCE = DefaultSchema.LOGIC_NAME;
/**
* 慢查询日志数据源(代理)
*/
public static final String MYSQL_DATASOURCE = "mysql_proxy";
/**
* information_schema数据源(代理)
*/
public static final String INFORMATION_SCHEMA_DATASOURCE = "informationSchema_proxy";
/**
* master数据源(代理)
*/
public static final String MASTER_DATASOURCE = "master_proxy";
}
ClassUtil.java
Class工具类
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import java.beans.Introspector;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Class工具类
*
* @since 2022-12-31 下午 3:14
*/
@Slf4j
public class ClassUtil extends cn.hutool.core.util.ClassUtil {
/**
* 字段缓存
*/
private static final Map<SerializableFunction<?, ?>, Field> CACHE = new ConcurrentHashMap<>();
private static final String GET = "get";
private static final String IS = "is";
private static final String LAMBDA$ = "lambda$";
private ClassUtil() {
throw new AssertionError("不可实例化");
}
/**
* 根据getter方法获取字段名
*/
public static <T, R> String getFieldName(SerializableFunction<T, R> function) {
Field field = getField(function);
return field.getName();
}
/**
* 根据getter方法获取字段
*/
public static <T, R> Field getField(SerializableFunction<T, R> function) {
return CACHE.computeIfAbsent(function, ClassUtil::findField);
}
private static <T, R> Field findField(SerializableFunction<T, R> function) {
Field field = null;
String fieldName = null;
try {
// 第1步:获取SerializedLambda
Method method = function.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(Boolean.TRUE);
SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
// 第2步:implMethodName 即为Field对应的Getter方法名
String implMethodName = serializedLambda.getImplMethodName();
if (implMethodName.startsWith(GET) && implMethodName.length() > GET.length()) {
fieldName = Introspector.decapitalize(implMethodName.substring(GET.length()));
} else if (implMethodName.startsWith(IS) && implMethodName.length() > IS.length()) {
fieldName = Introspector.decapitalize(implMethodName.substring(IS.length()));
} else if (implMethodName.startsWith(LAMBDA$)) {
throw new IllegalArgumentException(SerializableFunction.class.getSimpleName() + "不能传递lambda表达式,只能使用方法引用");
} else {
throw new IllegalArgumentException(implMethodName + "不是Getter方法引用");
}
// 第3步:获取的Class是字符串,并且包名是“/”分割,需要替换成“.”,才能获取到对应的Class对象
String declaredClass = serializedLambda.getImplClass().replace("/", ".");
Class<?> aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());
// 第4步:Spring中的反射工具类获取Class中定义的Field
field = ReflectionUtils.findField(aClass, fieldName);
} catch (Exception e) {
log.error("ClassUtil中findField()方法发生异常", e);
}
// 第5步:如果没有找到对应的字段应该抛出异常
if (field == null) {
throw new NoSuchFieldError(fieldName);
}
return field;
}
}
ShardingSphereAlgorithmUtil.java
ShardingSphere算法工具类
import org.apache.shardingsphere.infra.config.algorithm.ShardingSphereAlgorithmConfiguration;
import java.util.Properties;
/**
* ShardingSphere算法工具类
*
* @since 2023-7-29 下午 7:07
*/
public class ShardingSphereAlgorithmUtil {
public static ShardingSphereAlgorithmConfiguration buildInlineAlgorithmConfiguration(String algorithmExpression) {
Properties properties = new Properties();
properties.setProperty("algorithm-expression", algorithmExpression);
return new ShardingSphereAlgorithmConfiguration("INLINE", properties);
}
public static ShardingSphereAlgorithmConfiguration buildHashModAlgorithmConfiguration(String shardingCount) {
Properties properties = new Properties();
properties.setProperty("sharding-count", shardingCount);
return new ShardingSphereAlgorithmConfiguration("HASH_MOD", properties);
}
}
JwtUtil.java
Jwt-token工具类
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import common.model.dto.LoginToken;
import common.model.dto.ModifyEmailToken;
import common.model.dto.ModifyPasswordToken;
import common.util.Constants;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@Slf4j
public class JwtUtil {
/**
* 从请求中获取token
*/
public static String getTokenStringFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader(Constants.AUTHORIZATION_HEADER);
if (StrUtil.isNotBlank(bearerToken)) {
return bearerToken;
}
return null;
}
/**
* 从请求中获取当前登录用户的信息
*/
@Nullable
public static LoginToken getLoginTokenFromRequest(HttpServletRequest request) {
String token = getTokenStringFromRequest(request);
// 如果获取不到token的信息,说明没有登录的用户
if (StrUtil.isBlank(token)) {
return null;
}
return parseLoginToken(token);
}
/**
* 从请求中获取当前登录用户的用户名
*/
@Nullable
public static String getUsernameFromRequest(HttpServletRequest request) {
LoginToken loginToken = getLoginTokenFromRequest(request);
if (loginToken == null) {
return null;
}
return loginToken.getUsername();
}
}
12. 参考文章
spring boot:shardingsphere+druid多数据源整合seata分布式事务(spring boot 2.3.3):https://www.lmlphp.com/user/57801/article/item/2398339/
SpringBoot集成Shardingjdbc+seata AT模式:https://blog.csdn.net/qq_41563912/article/details/126305589
Shardingsphere-jdbc整合Feign、Seata AT 模式实现分布式事务的解决方案:https://blog.csdn.net/qq_42703153/article/details/129880291