關於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