分布式事务seata使用示例及注意事项

news2024/9/23 19:17:38

分布式事务seata使用示例及注意事项

  • 示例说明
  • 代码
    • 调用方(微服务A)
    • 服务方(微服务B)
  • 测试
    • 测试一 ,seata发挥作用,成功回滚!
    • 测试二:处理feignclient接口的返回类型从Integer变成String,其他的环境及代码完全一样,但是结果是“数据没有回滚:一致性未能保证”
    • ```结论一:要保证事务自动回滚,则需要有对应的异常抛出,才会触发自动回滚机制```
    • 测试三,针对如下场景:服务端(微服务B)抛出了异常,但是业务上又不能直接抛出,需要try...catch...捕获后并处理再返回。 在这种场景下如何保证分布式事务的回滚?—手动回滚
    • ```结论二: 业务上或交互上不允许抛出异常时,可采用上面的手动回滚的方式实现分布式事务```
    • 测试三 考虑这样一种场景:服务A调用服务B,服务B调用服务C ; 即是 A—>B—>C的情况;B即是服务端也是调用端
    • ```结论三: A——>B——>C,当C抛出异常时,可依次回滚! 注意,在调用端需加上@GlobalTransactional,如果对抛出的异常做了捕获处理则需要手动进行回滚!```
  • 延伸 关于openfeign调用:服务端返回异常信息,调用端接收异常的说明
    • 前面“测试一”中调用端接收到的异常是 服务端抛出的异常吗??
    • openfeign调用如何接收到服务端返回的真实的异常信息呢?
    • 关于feign.codec.ErrorDecoder的使用及注意

示例说明

  • 有两个微服务A、B。微服务A连接本地数据库ai_vs_remote_demo,并向表xl中插入一条数据;微服务B连接远程数据库hrm_db,并向表job_inf中插入一条数据;数据库ai_vs_remote_demo与hrm_db在不同的物理机、不同的地方。
  • 示例模拟这样一种情况:微服务A成功插入一条数据,微服务B在插入数据的业务过程中抛出异常,以此来验证分布式事务的回滚:微服务B的数据插入失败,同时微服务A插入的数据会被回滚!

代码

调用方(客户端):微服务A , 服务提供方(服务器端):微服务B

调用方(微服务A)

  • controller
@RestController
public class ClassForTest {
	Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private XlServiceImpl xlServiceImpl;

	@GetMapping("/dosth")
	public CheckListPo doSth() throws Exception {
		String ne = xlServiceImpl.doInsert();
		return null;
	}

}
  • service
@Service
@Transactional(rollbackFor = Exception.class)
public class XlServiceImpl {
	Logger logger = LoggerFactory.getLogger(this.getClass());
	@Autowired
	private XlMapper xlMapper;

	@Autowired
	private XlFeignClient xlFeignClient;


	@GlobalTransactional(rollbackFor = Exception.class)
	public String doInsert() throws Exception {
		// 演示如何获取seata的xid
		String xid = RootContext.getXID();
		logger.info("++++++++xid="+xid);
		// 本地数据库插入一条数据(向数据库ai_vs_remote_demo的表xl中插入一条数据)
		xlMapper.insert2();
		// openfeign调用远程微服务B(在微服务B中会向数据库hrm_db的表job_inf中插入一条数据)
		Integer ne = xlFeignClient.invokeBusi();
		return "";
	}
}
  • feigclient
@FeignClient(contextId = "xl20231108", name = "checklist-service")
public interface XlFeignClient {	
	@PostMapping("/checklistservice/xl/test")
	Integer invokeBusi() throws Exception;
}

服务方(微服务B)

  • controller
@RestController
public class XlContoller {

	@Autowired
	private XlBusiServiceImpl XlBusiServiceImpl;

	@PostMapping("/checklistservice/xl/test")
	public Integer invokeBusi() throws Exception {
		XlBusiServiceImpl.test();
		return 3;
	}
}
  • service ,这里会抛出异常:以测试分布式事务的回滚
@Service
@Transactional(rollbackFor = Exception.class)
public class XlBusiServiceImpl {
	
	@Autowired
	private XlBusiMapper xlBusiMapper;
	
	public String test() {
		xlBusiMapper.insert1();
		int x = 0;
		int y = 3 / x ;
		return "";
	}
	
}

测试

测试一 ,seata发挥作用,成功回滚!

  • 先看下测试前数据库的数据
    在这里插入图片描述
    在这里插入图片描述
  • 运行项目:http://localhost:8088/dosth
  1. 微服务A中的数据库操作语句xlMapper.insert2();执行成功!
    在这里插入图片描述

  2. 微服务B中抛出异常!
    在这里插入图片描述
    以上两点符合预期

  3. 微服务A中同样也抛出了异常,但是异常信息并不是微服务A传递过来的, 这是个疑点!后面会解释!

feign.codec.DecodeException: Could not extract response: no suitable HttpMessageConverter found for response type [class java.lang.Integer] and content type [text/html;charset=UTF-8]
	at feign.AsyncResponseHandler.decode(AsyncResponseHandler.java:119) ~[feign-core-10.12.jar:?]
	at feign.AsyncResponseHandler.handleResponse(AsyncResponseHandler.java:87) ~[feign-core-10.12.jar:?]
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-10.12.jar:?]
	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89) ~[feign-core-10.12.jar:?]
	at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100) ~[feign-core-10.12.jar:?]
	at com.sun.proxy.$Proxy236.invokeBusi(Unknown Source) ~[?:?]
	at com.omomcam.service.impl.xl.XlServiceImpl.doInsert(XlServiceImpl.java:43) ~[classes/:?]
  1. 最终数据库的结果,因为微服务A抛出了异常,所以自然会回滚插入的数据!
    在这里插入图片描述

测试二:处理feignclient接口的返回类型从Integer变成String,其他的环境及代码完全一样,但是结果是“数据没有回滚:一致性未能保证”

  • 同样运行项目并访问:http://localhost:8088/dosth。
  • 微服务B抛出同样的异常
    在这里插入图片描述
  • 但是,微服务A没有抛出异常!
    在这里插入图片描述
    也就是说,在微服务A中的如下代码一切正常执行完毕!
	@GlobalTransactional(rollbackFor = Exception.class)
	public String doInsert() throws Exception {
		// 演示如何获取seata的xid
		String xid = RootContext.getXID();
		logger.info("++++++++xid="+xid);
		// 本地数据库插入一条数据(向数据库ai_vs_remote_demo的表xl中插入一条数据)
		xlMapper.insert2();
		// openfeign调用远程微服务B(在微服务B中会向数据库hrm_db的表job_inf中插入一条数据)
		String ne = xlFeignClient.invokeBusi();
		return "";
	}

既然,程序正常执行完毕,那当然也不会存在数据的回滚了

  • 数据库验证:数据是否回滚
    刚刚访问http://localhost:8088/dosth 了两次
    在这里插入图片描述
  • 未回滚原因分析

上面已经提到了,因为微服务A中代码正常执行完成并没有抛出异常,所以是不会触发回滚机制的:

那为什么(对比“测试一”)将feignclient的中接口的返回类型从Integer改为String,调用方(微服务A)就不报错了呢?

原因参考:openfeign客户端A调用服务B,服务B抛出异常时,客户端A接收的几种情况

结论一:要保证事务自动回滚,则需要有对应的异常抛出,才会触发自动回滚机制

测试三,针对如下场景:服务端(微服务B)抛出了异常,但是业务上又不能直接抛出,需要try…catch…捕获后并处理再返回。 在这种场景下如何保证分布式事务的回滚?—手动回滚

  • 服务端抛出的异常被捕获了,而调用端又必须要有异常抛出才会回滚,这是相互矛盾的。那么要解决这个矛盾就只有手动进行回滚!

  • 手动回滚代码

 			try {
				GlobalTransactionContext.reload(RootContext.getXID()).rollback();
			} catch (TransactionException e1) {
				e1.printStackTrace();
			}

手动回滚的时机/位置:是在服务端还是调用端呢?
经测试手动回滚的代码,只能放在调用端,放在服务端无效!

  • 调用端示例代码
    特别说明:手动回滚时,不能同时使用io.seata.spring.annotation.GlobalTransactional注解和org.springframework.transaction.annotation.Transactional注解因为这里使用的是全局事务@GlobalTransactional的手动回滚功能,所以不能有@Transactional注解,至少要保证在@GlobalTransactional的方法上不能有@Transactional注解的作用(注意:这个时候也不能在类上加@Transactional了,因为其会作用于所有的方法)
package com.omomcam.service.impl.xl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.omomcam.dao.xl.XlMapper;
import com.omomcam.entity.common.ResponseData;
import com.omomcam.feign.XlFeignClient;

import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.spring.annotation.GlobalTransactional;
import io.seata.tm.api.GlobalTransactionContext;

@Service
//注释掉本注解,如果其他非@GlobalTransactional的方法上需要@Transactional注解,可单独加在方法上
//@Transactional(rollbackFor = Exception.class) 
public class XlServiceImpl {
	Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	private XlMapper xlMapper;

	@Autowired
	private XlFeignClient xlFeignClient;

	public void fb() {
		xlMapper.insert1();
		int y = 0;
		int x = 3/y;
	}

	@GlobalTransactional(rollbackFor = Exception.class)
	public String doInsert() throws Exception {
		// 本地数据库插入一条数据(向数据库ai_vs_remote_demo的表xl中插入一条数据)
		xlMapper.insert2();
		// openfeign调用远程微服务B(在微服务B中会向数据库hrm_db的表job_inf中插入一条数据)
		ResponseData<String> rd = xlFeignClient.invokeBusi();
		if (rd.getRespCode() != 200) {// 非200(失败)时,手动回滚事务,可根据具体业务自己灵活定义
			try {
				GlobalTransactionContext.reload(RootContext.getXID()).rollback();
			} catch (TransactionException e1) {
				e1.printStackTrace();
			}
		}
		return "";
	}

}
  • 服务端代码

抛出异常,但是会捕获并处理返回业务信息

抛出异常的代码:

package com.omomcam.checklistservice.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.omomcam.checklistservice.dao.XlBusiMapper;

import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.spring.annotation.GlobalTransactional;
import io.seata.tm.api.GlobalTransactionContext;

@Service
@Transactional(rollbackFor = Exception.class) 
public class XlBusiServiceImpl {
	
	@Autowired
	private XlBusiMapper xlBusiMapper;
	
	public String test() {
		xlBusiMapper.insert1();
		int x = 0;
		int y = 3 / x ;
		return "";
	}
}

捕获异常并返回自定义信息给调用端,代码如下:

package com.omomcam.checklistservice.controller;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import com.omomcam.checklistservice.service.impl.XlBusiServiceImpl;
import com.omomcam.entity.common.ResponseData;

@RestController
public class XlContoller {

	@Autowired
	private XlBusiServiceImpl XlBusiServiceImpl;

	@PostMapping("/checklistservice/xl/test")
	public ResponseData<String> invokeBusi(HttpServletResponse response) throws Exception {
		@SuppressWarnings("unchecked")
		ResponseData<String> rd = (ResponseData<String>) ResponseData.init();
		try {
			XlBusiServiceImpl.test();
		} catch (Exception e) {
			rd.setRespCode(400);
			rd.setRespMsg("服务端发生了异常");
			rd.setData("其他信息。。。");
		}
		return rd;
	}
}
  • 测试

说明:尽管服务端的异常被自己捕获了,返回了自定的相关信息,即是调用端没有收到任何异常。但是,调用端采用了手动回滚的方式,所以,调用端插入的数据仍然会被回滚!

运行前数据库中的数据:
在这里插入图片描述
执行过程
在这里插入图片描述
执行后数据库中的数据:
在这里插入图片描述
对比前后数据库及执行过程可知:数据回滚了!

结论二: 业务上或交互上不允许抛出异常时,可采用上面的手动回滚的方式实现分布式事务

测试三 考虑这样一种场景:服务A调用服务B,服务B调用服务C ; 即是 A—>B—>C的情况;B即是服务端也是调用端

如果B发生异常,则需要回滚A!上面的 测试二 已经测试了此种情况

如果C发生异常,则需要回滚B以及A!下面测试此种情况

  • 测试 (在上面“测试二”的基础上修改代码)

因为这里只有两个微服务A和B ,所以A处理是自己以外还扮演C的角色。即是调用链为 A——>B——>A。详细解释即是:调用端A——>服务端B 调用端B——>服务端A

  • 修改服务B的代码
    因为服务B既作为服务端有作为调用端,故需要保证服务端B中的数据库操作正常,以验证服务端A抛出异常后服务端B的数据可以回滚并且调用端A的数据库操作同样能够回滚!

保证XlBusiServiceImpl.test();调用正常:注释掉一下两行代码

package com.omomcam.checklistservice.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.omomcam.checklistservice.dao.XlBusiMapper;

import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.spring.annotation.GlobalTransactional;
import io.seata.tm.api.GlobalTransactionContext;

@Service
@Transactional(rollbackFor = Exception.class) 
public class XlBusiServiceImpl {
	
	@Autowired
	private XlBusiMapper xlBusiMapper;
	
	public String test() {
		xlBusiMapper.insert1();
//		int x = 0;
//		int y = 3 / x ;
		return "";
	}
	
}

服务端B及调用端B的完整代码如下,因为B这里又作为调用端所以需要加上 手动回滚 的代码以及注解 @GlobalTransactional(rollbackFor = Exception.class)

package com.omomcam.checklistservice.controller;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import com.omomcam.checklistservice.feign.OnlineReadingRestFeignClient;
import com.omomcam.checklistservice.service.impl.XlBusiServiceImpl;
import com.omomcam.entity.common.ResponseData;

import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.spring.annotation.GlobalTransactional;
import io.seata.tm.api.GlobalTransactionContext;

@RestController
public class XlContoller {

	@Autowired
	private XlBusiServiceImpl XlBusiServiceImpl;

	@Autowired
	private OnlineReadingRestFeignClient onlineReadingRestFeignClient;

	@GlobalTransactional(rollbackFor = Exception.class) // 需要加上 本注解
	@PostMapping("/checklistservice/xl/test")
	public ResponseData<String> invokeBusi(HttpServletResponse response) throws Exception {
		@SuppressWarnings("unchecked")
		ResponseData<String> rd = (ResponseData<String>) ResponseData.init();
		try {
			XlBusiServiceImpl.test();
		} catch (Exception e) {
			rd.setRespCode(400);
			rd.setRespMsg("服务端发生了异常");
			rd.setData("其他信息。。。");
			return rd; // 抛出异常后,直接返回
		}
		@SuppressWarnings("unchecked")
		ResponseData<Integer> resd = (ResponseData<Integer>) ResponseData.init();
		try {
			// 本"服务端"又反过去调用 "调用端" 即:本"服务端"现在扮演的是一个新的"调用端",原"调用端"变成了新的"服务端"
			resd = onlineReadingRestFeignClient.xlSeataTest();
			if (resd.getRespCode() != 200) { // 不等于200,说明发生了异常,则手动回滚
				try {
					GlobalTransactionContext.reload(RootContext.getXID()).rollback();
				} catch (TransactionException e1) {
					e1.printStackTrace();
				}
			}
		} catch (Exception e) {
			rd.setRespCode(resd.getRespCode());
			rd.setRespMsg(resd.getRespMsg());
			return rd;
		}
		return rd;
	}
}

  • 修改服务A的代码

增加提供服务的接口方法,同时要保证返回的转态码不等于200:使xlServiceImpl.fb();抛出异常;详细见如下代码:

	@PostMapping("/seata/xl/test")
	public ResponseData<Integer> xlSeataTest() {
		@SuppressWarnings("unchecked")
		ResponseData<Integer> rd = (ResponseData<Integer>) ResponseData.init();
		try {
			xlServiceImpl.fb();
		} catch (Exception e) {
			rd.setRespCode(500);
		}
		return rd;
	}

完整controller代码:

package com.omomcam.controller.xl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import com.omomcam.entity.CheckListPo;
import com.omomcam.entity.common.ResponseData;
import com.omomcam.service.impl.xl.XlServiceImpl;

@RestController
public class ClassForTest {
	Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private XlServiceImpl xlServiceImpl;

	@GetMapping("/dosth")
	public CheckListPo doSth() throws Exception {
		String ne = xlServiceImpl.doInsert();
		return null;
	}
	
	@PostMapping("/seata/xl/test")
	public ResponseData<Integer> xlSeataTest() {
		@SuppressWarnings("unchecked")
		ResponseData<Integer> rd = (ResponseData<Integer>) ResponseData.init();
		try {
			xlServiceImpl.fb();
		} catch (Exception e) {
			rd.setRespCode(500);
		}
		return rd;
	}
}

完整service代码:

package com.omomcam.service.impl.xl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.omomcam.dao.xl.XlMapper;
import com.omomcam.entity.common.ResponseData;
import com.omomcam.feign.XlFeignClient;

import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.spring.annotation.GlobalTransactional;
import io.seata.tm.api.GlobalTransactionContext;

@Service
//注释掉本注解,如果其他非@GlobalTransactional的方法上需要@Transactional注解,可单独加在方法上
//@Transactional(rollbackFor = Exception.class) 
public class XlServiceImpl {
	Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	private XlMapper xlMapper;

	@Autowired
	private XlFeignClient xlFeignClient;
	
	@Transactional(rollbackFor = Exception.class) 
	public void fb() {
		xlMapper.insert1();
		int y = 0;
		int x = 3/y;
	}

	@GlobalTransactional(rollbackFor = Exception.class)
	public String doInsert() throws Exception {
		// 本地数据库插入一条数据(向数据库ai_vs_remote_demo的表xl中插入一条数据)
		xlMapper.insert2();
		// openfeign调用远程微服务B(在微服务B中会向数据库hrm_db的表job_inf中插入一条数据)
		ResponseData<String> rd = xlFeignClient.invokeBusi();
		if (rd.getRespCode() != 200) { // 非200(失败)时,手动回滚事务
			try {
				GlobalTransactionContext.reload(RootContext.getXID()).rollback();
			} catch (TransactionException e1) {
				e1.printStackTrace();
			}
		}
		return "";
	}

}

  • 测试

先看调用端A的执行情况:
在这里插入图片描述
再看服务端B执行情况
在这里插入图片描述
再看调用端B的执行情况:
在这里插入图片描述
执行前的数据库中数据的情况
在这里插入图片描述
再执行一遍
执行后的数据库中数据情况
在这里插入图片描述
截图说明,数据成功回滚: C异常回滚B以及回滚A

结论三: A——>B——>C,当C抛出异常时,可依次回滚! 注意,在调用端需加上@GlobalTransactional,如果对抛出的异常做了捕获处理则需要手动进行回滚!

延伸 关于openfeign调用:服务端返回异常信息,调用端接收异常的说明

前面“测试一”中调用端接收到的异常是 服务端抛出的异常吗??

显示不是的!

服务端返回的是
/ by zero 异常
在这里插入图片描述

调用端接收到的是
HttpMessageConverter 转换异常

feign.codec.DecodeException: Could not extract response: no suitable HttpMessageConverter found for response type [class java.lang.Integer] and content type [text/html;charset=UTF-8]
	at feign.AsyncResponseHandler.decode(AsyncResponseHandler.java:119) ~[feign-core-10.12.jar:?]
	at feign.AsyncResponseHandler.handleResponse(AsyncResponseHandler.java:87) ~[feign-core-10.12.jar:?]
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-10.12.jar:?]
	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89) ~[feign-core-10.12.jar:?]
	at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100) ~[feign-core-10.12.jar:?]
	at com.sun.proxy.$Proxy236.invokeBusi(Unknown Source) ~[?:?]
	at com.omomcam.service.impl.xl.XlServiceImpl.doInsert(XlServiceImpl.java:43) ~[classes/:?]

参考:openfeign客户端A调用服务B,服务B抛出异常时,客户端A接收的几种情况

openfeign调用如何接收到服务端返回的真实的异常信息呢?

参考:
在这里插入图片描述

摘自: openfeign集成sentinel实现服务降级

关于feign.codec.ErrorDecoder的使用及注意

1、实现ErrorDecoder接口

package com.omomcam.config;

import feign.Response;
import feign.Response.Body;
import feign.codec.ErrorDecoder;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.tm.api.GlobalTransactionContext;

public class CustomErrorDecoder implements ErrorDecoder {
	
	private final ErrorDecoder defaultErrorDecoder = new Default();

	@Override
	public Exception decode(String methodKey, Response response) {
        Body body = response.body();
        String bodyStr = body.toString();
		// 在这里进行自定义的异常处理逻辑
        // 例如,你可以根据 HTTP 状态码区分不同的异常类型
        if (response.status() == 404) {
            // 处理 404 错误
//            return new NotFoundException("Not Found");
        }
        
//        try {
//			GlobalTransactionContext.reload(RootContext.getXID()).rollback();
//		} catch (TransactionException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}

        // 如果没有匹配到特定的异常,使用默认的 ErrorDecoder
        return defaultErrorDecoder.decode(methodKey, response);
	}
}

2、将实现类配置进spring环境

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import feign.codec.ErrorDecoder;

@Configuration
public class MyConfig {
 
    @Bean
    public ErrorDecoder errorDecoder() {
        return new CustomErrorDecoder();
    }
 
}

3, 特别注意!!并不是服务器端只要抛出了异常就会触发下面的方法执行,只有response的status不是200的时候才会触发

@Override
	public Exception decode(String methodKey, Response response) {
        Body body = response.body();
        String bodyStr = body.toString();
		// 在这里进行自定义的异常处理逻辑
        // 例如,你可以根据 HTTP 状态码区分不同的异常类型
        if (response.status() == 404) {
            // 处理 404 错误
//            return new NotFoundException("Not Found");
        }
        
//        try {
//			GlobalTransactionContext.reload(RootContext.getXID()).rollback();
//		} catch (TransactionException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}

        // 如果没有匹配到特定的异常,使用默认的 ErrorDecoder
        return defaultErrorDecoder.decode(methodKey, response);
	}

什么情况response的status不是200?

  • openfeign自动返回非200的情况
  • 在程序中手动设置response的status的值
import javax.servlet.http.HttpServletResponse;

public ResponseData<String> invokeBusi(HttpServletResponse response) throws Exception {
		try {
		XlBusiServiceImpl.test();
		} catch (Exception e) {
		  	response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	       // 或者直接设任意非200类型的值
	        response.setStatus(404);
			ne.setSs("报错了,报错信息:"+e.getMessage());
		}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1315730.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数理统计基础:参数估计与假设检验

在学习机器学习的过程中&#xff0c;我充分感受到概率与统计知识的重要性&#xff0c;熟悉相关概念思想对理解各种人工智能算法非常有意义&#xff0c;从而做到知其所以然。因此打算写这篇笔记&#xff0c;先好好梳理一下参数估计与假设检验的相关内容。 1 总体梳理 先从整体结…

OceanBase数据库初识

文章目录 说明分布式数据库发展发展历史OceanBase和传统数据库的对比总结 OceanBase数据库产品简介应用案例 OceanBase数据库产品OceanBase数据库内核OceanBase开发者中心&#xff08;ODC&#xff09;产品架构OMS核心功能简介 说明 本文仅供学习和交流学习内容参考官方的培训资…

年底了,千万不要跳槽..

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

关于Linux你必须知道的五件事

Linux是一种开源操作系统 (OS)。操作系统是直接管理系统硬件和资源&#xff08;如 CPU、内存和存储&#xff09;的软件。操作系统位于应用程序和硬件之间&#xff0c;并在所有软件和执行工作的物理资源之间建立连接。 俄罗斯军方计划用 Astra Linux 取代 Windows&#xff01;为…

【数据结构】双链表的定义和操作

目录 1.双链表的定义 2.双链表的创建和初始化 3.双链表的插入节点操作 4.双链表的删除节点操作 5.双链表的查找节点操作 6.双链表的更新节点操作 7.完整代码 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助…

RuoYi-Cloud诺依微服务项目

1、架构图 从图中解析出RuoYi-Cloud 使用微服务技术栈 网关&#xff1a;Gateway远程调用&#xff1a;Ribbon/Feign注册中心&#xff1a;Nacos Discovery熔断降级&#xff1a;Sentinel配置中心&#xff1a;Nacos Config链路追踪&#xff1a;Sleuth ZipKin/SkyWalking &#x…

leetcode(力扣) 89. 格雷编码 (规律题)

文章目录 题目描述思路分析完整代码 题目描述 n 位格雷码序列 是一个由 2n 个整数组成的序列&#xff0c;其中&#xff1a; 每个整数都在范围 [0, 2n - 1] 内&#xff08;含 0 和 2n - 1&#xff09; 第一个整数是 0 一个整数在序列中出现 不超过一次 每对 相邻 整数的二进制表…

vue3 使用antd 报错Uncaught TypeError--【已解决】

问题现象 使用最基本的 ant-design-vue 按钮demo 都报错 报错文字如下 Uncaught TypeError: Cannot read properties of undefined (reading value)at ReactiveEffect.fn (ant-design-vue.js?v597f5366:6693:87)at ReactiveEffect.run (chunk-K2VKR2AM.js?v25c381c3:461:…

用文本创建图表的工具PlantUML

什么是 PlantUML &#xff1f; PlantUML 是一种开源工具&#xff0c;允许用户从纯文本语言创建图表。除了各种 UML 图之外&#xff0c;PlantUML 还支持各种其他软件开发相关格式&#xff0c;以及 JSON 和 YAML 文件的可视化。PlantUML 语言是特定领域语言的一个示例。 什么是 P…

Shopee ERP:提升电商管理效率的终极解决方案

Shopee ERP&#xff08;Enterprise Resource Planning&#xff0c;企业资源规划&#xff09;是一款专为Shopee卖家设计的集成化电商管理软件。通过使用Shopee ERP系统&#xff0c;卖家可以更高效地管理他们的在线商店&#xff0c;实现库存管理、订单处理、物流跟踪、财务管理、…

【理论篇】SaTokenException: 非Web上下文无法获取Request问题解决 -理论篇

在我们使用sa-token安全框架的时候&#xff0c;有时候会提示&#xff1a;SaTokenException:非Web上下文无法获取Request 错误截图&#xff1a; 在官方网站中&#xff0c;查看常见问题排查&#xff1a; 错误追踪&#xff1a; 跟着源码可以看到如下代码&#xff1a; 从源码中&a…

【Spring教程30】Spring框架实战:从零开始学习SpringMVC 之 Rest风格简介与RESTful入门案例

目录 1 REST简介2 RESTful入门案例2.1 环境准备2.2 思路分析2.3 修改RESTful风格 3 知识点总结 欢迎大家回到《Java教程之Spring30天快速入门》&#xff0c;本教程所有示例均基于Maven实现&#xff0c;如果您对Maven还很陌生&#xff0c;请移步本人的博文《如何在windows11下安…

我的世界合成表大全(最新完整版)

我的世界合成表配方是什么? 我的世界是一款非常有趣的高自由度的沙盒游戏&#xff0c;游戏中玩家可以根据合成配方制作各种各样的物品。今天小编就为大家带来我的世界合成表大全(最新完整版)&#xff0c;希望可以帮到大家。 我的世界合成表大全(最新完整版) 基础物品合成表&a…

知识付费小程序开发:构建个性化学习平台的技术实践

随着在线学习和知识付费的兴起&#xff0c;开发一款知识付费小程序成为了创新的热点之一。本文将通过使用Node.js、Express和MongoDB为例&#xff0c;演示如何构建一个基础的知识付费小程序后端&#xff0c;并实现用户认证和知识内容管理。 1. 初始化项目 首先&#xff0c;确…

eNSP小实验(ACL和NAT)

一.ACL 实验目的&#xff1a;过滤流量&#xff0c;然后匹配规划后&#xff0c;判断该流量通过或拒绝 1.拓扑图 2.配置 基本ACL 其它同理配置 R1 [Huawei]sys R1 [R1]int g0/0/0 [R1-GigabitEthernet0/0/0]dis th [V200R003C00] # interface GigabitEthernet0/0/0 # return…

基础IO --- 下

目录 1. 理解文件系统中inode的概念 1.1. 了解磁盘 1.1.1. 认识磁盘 1.1.2. 磁盘的物理结构 1.1.3. 简单了解磁盘如何读写数据的 1.1.4. 磁头和盘面没有物理上的接触 1.1.5. 扇区的了解 1.1.6. 如何在物理上找到一个具体的扇区 1.2. 站在OS的角度看待磁盘 1.2.1. …

走进暄桐教室 一起观看暄桐同学作品及感受

暄桐是一间传统美学教育教室&#xff0c;创办于2011年&#xff0c;林曦是创办人和授课老师&#xff0c;教授以书法为主的传统文化和技艺&#xff0c;旨在以书法为起点&#xff0c;亲近中国传统之美&#xff0c;以实践和所得&#xff0c;滋养当下生活。其实&#xff0c;暄桐教室…

Linux 系统 yum 安装 jdk1.8

1、首先检查是否存在jdk java -version上图这样就是系统没有找到已经安装的jdk 2.查看jdk版本列表 yum -y list java*执行此命令会显示所有版本 jdk 安装包 3、下载安装jdk 这里安装的是jdk1.8 yum install java-1.8.0-openjdk-devel.x86_64这里输入回车y继续安装 4、再次检…

低代码开发入局,同飞股份应用云表自主开发MES管理系统

近日&#xff0c;为了贯彻落实《“十四五”智能制造发展规划》&#xff0c;推动中国从制造大国向制造强国转变&#xff0c;工业和信息化部发布了2023年度“智能制造优秀场景”名单。经过省级有关部门和中央企业的推荐、专家评审、网上公示等程序&#xff0c;同飞股份凭借其“先…

LeetCode-反转链表问题

1.反转链表 题目描述&#xff1a; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 思路&#xff1a; 反转链表也就是链表倒置&#xff0c;我一直以来的办法就是先建立一个头节点&#xff0c;之后再遍历链表来进行头插。 代码&#xff1…