关于Seata配置, 一步一个坑, 配置过程中0.9.0~1.2.0版本全下载也没有成功(非Feign降级可以回滚, 但是数据库事务相关表没任何数据出现), 官方文档和Demo描述都是1.0.0之前老版本的, 1.0.0以上要么只有简单说明, 要么直接项目贴jar包, 终于是自己摸索出来了, 希望能让Springcloud整合Seata的同学少走点坑.
下面开始详细的配置说明.
版本信息
Jar版本信息
JDK : 1.8.0
Springboot: 2.2.6.RELEASE
Nacos: 2.2.1.RELEASE
OpenFeign: 2.2.2.RELEASE
MybatisPlus: 3.3.1.tmp
Nacos 及 Seata 版本
Nacos: 1.2.1
Seata: 1.2.0
项目相关其他Jar版本
Sentinel-dashboard: 1.7.2
spring-cloud-starter-alibaba-sentinel: 2.2.1.RELEASE
druid: 1.1.22
这里也不描述官方的转账Demo, 没什么用, 只要配置好了事务过程中Seata数据库有信息就是可用的.
Seata下载地址
Nacos跳过
解压后修改三个配置文件. file.conf, file.conf.example, registry.conf.
file.conf 修改mode=‘db’, 下方Seata数据库地址账号密码
## transaction log store, only used in seata-server
store {
## store mode: file、db
mode = "db"
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "mysql"
password = "mysql"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
file.conf.example 修改mode=‘db’, 下方Seata数据库地址账号密码
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
# the client batch send request enable
enableClientBatchSendRequest = false
#thread factory for netty
threadFactory {
bossThreadPrefix = "NettyBoss"
workerThreadPrefix = "NettyServerNIOWorker"
serverExecutorThreadPrefix = "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"
}
## transaction log store, only used in server side
store {
## store mode: file、db
mode = "db"
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "mysql"
password = "mysql"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
}
}
## server configuration, only used in server side
server {
recovery {
#schedule committing retry period in milliseconds
committingRetryPeriod = 1000
#schedule asyn committing retry period in milliseconds
asynCommittingRetryPeriod = 1000
#schedule rollbacking retry period in milliseconds
rollbackingRetryPeriod = 1000
#schedule timeout retry period in milliseconds
timeoutRetryPeriod = 1000
}
undo {
logSaveDays = 7
#schedule delete expired undo_log in milliseconds
logDeletePeriod = 86400000
}
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
maxCommitRetryTimeout = "-1"
maxRollbackRetryTimeout = "-1"
rollbackRetryTimeoutUnlockEnable = false
}
## metrics configuration, only used in server side
metrics {
enabled = false
registryType = "compact"
# multi exporters use comma divided
exporterList = "prometheus"
exporterPrometheusPort = 9898
}
registry.conf registry.type=‘nacos’ 并标明nacos端口
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "localhost:8848"
namespace = ""
cluster = "default"
username = ""
password = ""
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = 0
password = ""
cluster = "default"
timeout = 0
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 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
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
username = ""
password = ""
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
启动Nacos, 启动Seata, 若Seata运行无报错且Nacos服务列表存在Seata服务则第一步成功.
Seata数据库配置, 创建表
-- the table to store GlobalSession data
drop table if exists `global_table`;
create table `global_table` (
`xid` varchar(128) not null,
`transaction_id` bigint,
`status` tinyint not null,
`application_id` varchar(32),
`transaction_service_group` varchar(32),
`transaction_name` varchar(128),
`timeout` int,
`begin_time` bigint,
`application_data` varchar(2000),
`gmt_create` datetime,
`gmt_modified` datetime,
primary key (`xid`),
key `idx_gmt_modified_status` (`gmt_modified`, `status`),
key `idx_transaction_id` (`transaction_id`)
);
-- the table to store BranchSession data
drop table if exists `branch_table`;
create table `branch_table` (
`branch_id` bigint not null,
`xid` varchar(128) not null,
`transaction_id` bigint ,
`resource_group_id` varchar(32),
`resource_id` varchar(256) ,
`lock_key` varchar(128) ,
`branch_type` varchar(8) ,
`status` tinyint,
`client_id` varchar(64),
`application_data` varchar(2000),
`gmt_create` datetime,
`gmt_modified` datetime,
primary key (`branch_id`),
key `idx_xid` (`xid`)
);
-- the table to store lock data
drop table if exists `lock_table`;
create table `lock_table` (
`row_key` varchar(128) not null,
`xid` varchar(96),
`transaction_id` long ,
`branch_id` long,
`resource_id` varchar(256) ,
`table_name` varchar(32) ,
`pk` varchar(36) ,
`gmt_create` datetime ,
`gmt_modified` datetime,
primary key(`row_key`)
);
其他各服务数据库添加undo_log表
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
服务配置
Pom.xml 导入Jar. 每个服务都需导入Seata相关包
<properties>
<java.version>1.8</java.version>
<nacos.version>2.2.1.RELEASE</nacos.version>
<boot.version>2.2.6.RELEASE</boot.version>
<feign.version>2.2.2.RELEASE</feign.version>
</properties>
<!--Seata 包-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2.2.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<!--openfeign 支持解析MVC注解接口-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${feign.version}</version>
</dependency>
<!--Nacos配置-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${nacos.version}</version>
</dependency>
<!--Sentinel 流量哨兵-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${nacos.version}</version>
</dependency>
<!--Sentinel-Nacos动态配置-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.7.2</version>
</dependency>
<!--ORM-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
<exclusions>
<exclusion>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</exclusion>
</exclusions>
</dependency>
yml配置
项目使用Nacos动态配置, 我这里做了拆分
bootstrap优先级大于application, 所有拉取Nacos配置的要写在此处, 不然读取不到就会报错.
bootstrap.yml 主要是读取Nacos配置, 如下:
bootstrap.yml
spring:
nacos-host: localhost
nacos-port: 8848
application:
name: xxx-server
cloud:
nacos:
discovery:
server-addr: ${spring.nacos-host}:${spring.nacos-port}
config:
server-addr: ${spring.nacos-host}:${spring.nacos-port}
file-extension: yaml
#Sentinel-Nacos 配置
sentinel:
datasource:
ds:
nacos:
server-addr: ${spring.nacos-host}:${spring.nacos-port}
data-id: ${spring.application.name}-sentinel
groupId: FLOW
rule-type: flow
application.yml
spring:
profiles:
active: dev
application-dev.yml
server:
port: 8002
spring:
application:
name: xxx-server # 应用名称 自定义
host: localhost
# druid 数据源配置
datasource:
url: jdbc:mysql://localhost:3308/xxx业务数据库?allowMultiQueries=true&autoReconnect=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useUnicode=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: mysql
password: mysql
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
filters: stat,wall,slf4j
maxActive: 20
initialSize: 5
maxWait: 60000
minIdle: 5
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
main:
allow-bean-definition-overriding: true
# 连接控制台
cloud:
sentinel:
transport:
port: 7001
dashboard: ${spring.application.host}:8080
mybatis-plus:
global-config:
db-config:
id-type: auto
# 表名前缀
table-prefix: 'xxx'
configuration:
# 驼峰下划线映射
map-underscore-to-camel-case: true
cache-enabled: false
# config-location: classpath:mybatis/mybatis.cfg.xml
mapper-locations: classpath:mapper/*.xml
# 开启Sentinel熔断器
feign:
sentinel:
enabled: true
# 饿加载开启 Feign 预加载, 防止第一次请求超时
ribbon:
eager-load:
enabled: true
clients: xxx-server, xxx-server
seata:
application-id: ${spring.application.name} # Seata 应用名称,默认使用 ${spring.application.name}
tx-service-group: default # Seata 事务组, 高版本没找到相关配置, 是否可配置未知 选用默认default
# 服务配置项
service:
# 虚拟组和分组的映射 1.0.0以上好像将vgroup-mapping 改为 vgroupMapping, 此处是否影响未测试
vgroupMapping:
# 此处Key对应 tx-service-group 的 Value, 此处 value 默认 default
default: default
# 分组和 Seata 服务的映射 默认端口8091
grouplist:
default: 127.0.0.1:8091
调用说明: A服务调用B服务, 两边都是insert, 在B服务插入成功后抛出异常, 测试是否回滚.
A服务启动类添加Feign注解, @EnableFeignClients, @MapperScan(“dao包路径”)
A服务编写业务代码, 使用MybatisPlus内置方法, 其他写法注入mapper接口调用方法就好.
主分支方法添加事务注解@GlobalTransactional(rollbackFor = Exception.class),
分支事务注解无需添加
package com.mis.rbac.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mis.common.constants.RespData;
import com.mis.common.rbac.entity.Dept;
import com.mis.common.utils.DateUtils;
import com.mis.rbac.dao.DeptMapper;
import com.mis.rbac.feign.BankServiceFeign;
import com.mis.rbac.service.DeptService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {
@Autowired
BankServiceFeign bankServiceFeign;
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public RespData addDept() {
Dept dept = new Dept();
dept.setDeptName("asdasdsa");
dept.setDeptPid(0L);
dept.setCreateTime(DateUtils.getCurrentDate("", String.class));
if(dept.insert()){
bankServiceFeign.tran();
}
return RespData.success();
}
}
Feign接口
package com.mis.rbac.feign;
import com.mis.common.constants.RespData;
import com.mis.rbac.feign.impl.BankServiceFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
@FeignClient(name = "firm-server"/*, fallbackFactory= BankServiceFallbackFactory.class*/)
public interface BankServiceFeign {
@PostMapping("tran")
public RespData tran();
}
Controller 跳过.
B服务
package com.mis.firm.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mis.common.constants.RespData;
import com.mis.common.firm.Bank;
import com.mis.firm.dao.BankMapper;
import com.mis.firm.service.BankService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BankServiceImpl extends ServiceImpl<BankMapper, Bank> implements BankService {
@Override
public RespData tran() throws Exception {
System.out.println("执行添加Bank.");
Bank bank = new Bank();
bank.setBankAccount("123456");
bank.setBankAddr("Transactional");
bank.setBankName("事务");
bank.setBankPhone("123456789");
bank.setDel(0);
if(bank.insert()){
throw new Exception("手动异常, 事务回滚");
}
return RespData.success();
}
}
启动Nacos, Seata后, 启动服务
若Seata控制台输出服务相关信息及数据库信息则连接成功, 如下:
若启动过程中出现 no available service ‘null’ found, please make sure registry config correct或DataSource错误,
说明未连接上Seata.
一般是Jar有冲突
spring-cloud-starter-alibaba-seata: 2.2.1.RELEASE 此版本与 seata-spring-boot-starter:1.1.0 冲突
更换为 seata-spring-boot-starter:1.2.0
其他情况: ClassNotFoundException:io.seata.spring.annotation.datasource.SeataDataSourceBeanPostProcessor
缺少Jar : seata-all
推荐使用seata-spring-boot-starter
启动成功并控制台无错误信息, 开始调用接口测试.
若主事务与分支事务均回滚, 则又进一步成功,
BUG断点测试, 拦截过程中查看Seata数据库表和服务undo_log表
若表存在数据, 如:
则完成配置
Feign降级回滚(动态回滚)
使用Feign降级后, Seata不回滚.
需手动配置回滚
官方文档
RootContext.getXID() 获取当前XID后执行手动回滚
编写AOP, 切点事务注解所在的实现类, 各服务都添加此切面类
package com.mis.rbac.aop;
import java.lang.reflect.Method;
import com.mis.common.constants.RespData;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.tm.api.GlobalTransaction;
import io.seata.tm.api.GlobalTransactionContext;
import lombok.extern.slf4j.Slf4j;
/**
* Seata 动态事务回滚切面类 (Feign熔断降级回滚)
*/
@Slf4j
@Aspect
@Component
public class AtFeignTransactionalAop {
@Before("execution(* com.mis.rbac.service.*.*(..))")
public void before(JoinPoint joinPoint) throws TransactionException {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
log.info("拦截到需要分布式事务的方法," + method.getName());
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
// 超时时间 , 所在服务
tx.begin(300000, "rbac-server");
log.info("创建分布式事务id:{}", tx.getXid());
}
@AfterThrowing(throwing = "e", pointcut = "execution(* com.mis.rbac.service.*.*(..))")
public void doRecoveryActions(Throwable e) throws TransactionException {
log.info("方法执行异常:{}", e.getMessage());
if (!StringUtils.isBlank(RootContext.getXID())) {
log.info("分布式事务Id:{}, 手动回滚!", RootContext.getXID());
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
}
}
@AfterReturning(value = "execution(* com.mis.rbac.service.*.*(..))", returning = "result")
public void afterReturning(JoinPoint point, Object result) throws TransactionException {
log.info("方法执行结束:{}", result);
// 方法返回值 RespData是自定义的统一返回类
RespData respData = (RespData)result;
if (respData.getCode() != 0) {
if (!StringUtils.isBlank(RootContext.getXID())) {
log.info("分布式事务Id:{}, 手动回滚!", RootContext.getXID());
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
}
}
}
}
统一异常
package com.mis.rbac.exception;
import com.mis.common.enums.Code;
/**
* @ClassName GlobalException
* @description: 自定义异常
**/
public class GlobalException extends RuntimeException {
/**
* 异常状态码
*/
private Integer code;
/**
* 异常信息
*/
private String message;
private String e;
public GlobalException(Code enums){
super();
this.code=enums.getCode();
this.message=enums.getMsg();
}
public GlobalException(Code enums, String e){
super();
this.code=enums.getCode();
this.message=enums.getMsg();
this.e = e;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getE() {
return e;
}
public void setE(String e) {
this.e = e;
}
}
package com.mis.rbac.exception;
import com.mis.common.constants.RespData;
import com.mis.common.enums.Code;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
/**
* @ClassName GlobalExceptionHandler
* @Description TODO 全局异常处理类
* @Version 1.0.0
**/
@Slf4j
@ResponseBody
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(GlobalException.class)
public RespData handlerGlobalException(GlobalException e) {
e.printStackTrace();
log.error("\r\n异常信息 : \r\n[\r\n 错误代码 >>>>>>> { "+ e.getCode() +" } , 错误信息 >>>>>>> { "+e.getE()+" } \r\n].");
return RespData.error(Code.EXCEPTION_ERROR);
}
@ExceptionHandler(Exception.class)
public RespData handleException(Exception e) {
e.printStackTrace();
log.error("\r\n系统系统异常 >>>>>> { "+e.getMessage()+" } , \r\n错误信息 >>>>>>> {} ,\r\n { "+getExceptionAllinfo(e)+" } \r\n");
return RespData.error(Code.EXCEPTION_ERROR);
}
/**
* 验证异常
* @param e
* @return
* @throws BindException
*/
@ExceptionHandler(value = BindException.class)
public RespData handleBindException(BindException e) {
FieldError fieldError = e.getFieldError();
StringBuilder sb = new StringBuilder();
sb.append(fieldError.getField()).append(" =[ ").append(fieldError.getRejectedValue()).append(" ] ")
.append(fieldError.getDefaultMessage());
log.error("\r\n参数验证异常 >>>>>> 错误信息 : " + sb.toString());
return RespData.error(-1,sb.toString());
}
@ExceptionHandler(value = ConstraintViolationException.class)
public Object ConstraintViolationExceptionHandler(ConstraintViolationException exception){
StringBuilder sb = new StringBuilder();
int i = 0;
for (ConstraintViolation<?> violation : exception.getConstraintViolations()) {
if(i == 0){
sb.append(violation.getMessageTemplate());
}
i++;
}
log.error("\r\n参数验证异常 >>>>>> 错误信息 : " + sb.toString() + "\r\n");
return RespData.error(4000,sb.toString());
}
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Object MethodArgumentNotValidHandler(MethodArgumentNotValidException exception){
StringBuilder sb = new StringBuilder();
for (FieldError error : exception.getBindingResult().getFieldErrors()) {
sb.append(error.getField()).append(" =[ ").append(error.getRejectedValue()).append(" ] ")
.append(error.getDefaultMessage());
}
log.error("\r\n参数验证异常 >>>>>> 错误信息 : " + sb.toString());
return RespData.error(4000,sb.toString());
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public RespData handleException(HttpRequestMethodNotSupportedException e) {
System.err.println("\n************************************************************************************");
System.err.println("\n* 请求异常 : ---> [ { "+e.getMessage()+" } ] . *");
System.err.println("\n************************************************************************************\n");
return RespData.error(4000, e.getMessage());
}
public String getExceptionAllinfo(Exception ex) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream pout = new PrintStream(out);
ex.printStackTrace(pout);
String ret = new String(out.toByteArray());
pout.close();
try {
out.close();
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
}
return ret;
}
}
动态回滚 已完成.
参考:
https://seata.io/zh-cn/blog/seata-spring-boot-aop-aspectj.html