当前场景:app接口服务调用A服务和B服务。app接口内会回滚,A服务和B服务不会回滚。
App接口
@GlobalTransactional(rollbackFor = Exception.class)
public Result uploadFile(SaveLogDataReq req) {
System.out.println("seata全局事务id====================>"+ RootContext.getXID());
servieFeignA.addAAA(req);
servieFeignB.addBBB(req);
int i = 1/0;
return Resu.ok();
}
A服务
public Result addAAA(SaveLogDataReq req) {
System.out.println("seata全局事务id====================>"+ RootContext.getXID());
//todo AAA代码逻辑......
return Resu.ok();
}
B服务
public Result addBBB(SaveLogDataReq req) {
System.out.println("seata全局事务id====================>"+ RootContext.getXID());
//todo BBB代码逻辑......
return Resu.ok();
}
分析:查看打印日志显示,A服务和B服务的xid为空,或者与APP接口服务的xid不一致。
解决方案:
方案一,在fein远程调用的时候将当前的xid传入header
1,增加request拦截器
public class MultipartSupportConfig implements RequestInterceptor {
/**
* 解决服务直接调用请求头不传递的问题
* @param template
*/
@Override
public void apply(RequestTemplate template) {
//解决不传递请求头中的token
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null){
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
//可以在这里将自定义请求头传递进去, key 请求, value 值
//处理上游请求头信息,传递时继续携带
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
template.header(name, values);
}
}
// 解决seata的xid未传递
String xid = RootContext.getXID();
template.header(RootContext.KEY_XID, xid);
}
}
2,在调用A/B服务的fein注解上加入上面的request拦截器(configuration = MultipartSupportConfig.class)配置。
@FeignClient(contextId="LogDataService",value = "LogDataService",configuration = MultipartSupportConfig.class)
public interface ServieFeignA {
@PostMapping(value = "/api/logdata/v1/log/save",consumes = MediaType.APPLICATION_JSON_VALUE)
public Result<String> save(@RequestBody SaveLogDataReq req);
}
这个方案比较繁琐,不优先考虑。
方案二,使用spring-cloud-starter-alibaba-seata自动配置(推荐)
之前使用的是seata-spring-boot-starter配置,这里的seata自动配置不会对feign进行处理,所以fein调用不会传xid,导致全局事务未生效。
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
所以去掉这个maven配置,使用spring-cloud-starter-alibaba-seata替代。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
这里会对fein进行seata相关的配置。
注意:
1,异常需要层层往上抛,如果你在子服务将异常处理的话(比如全局异常处理GlobalExceptionHandler),seata会认为你已经手动处理了异常。
2,出现事务失效的情况下,优先检查 RootContext.getXID() ,xid是否传递且一致。
3,主服务加上@GlobalTransactional注解即可,被调用服务不用加@GlobalTransactional和@Transactional.
4,@GlobalTransactional(rollbackFor = Exception.class)最好加上rollbackFor = Exception.class,表示遇到Exception都回滚,不然遇到有些异常(如自定义异常)则不会回滚。