为了避免错误直接抛出去给用户,我们一般都是try catch 处理。

但是存在这样一个问题,我们try catch 捕获了,这时候系统就不会回滚了,我们需要手动回滚。

如果我们一个新增方法出现了异常,我们想给用户提示一个系统异常,但是这之前我们知道某个异常是要给用户明确提示的,比如该用户缺少手机号。


一、使用try catch的方式解决上面的问题

@RestController
public class TestController {

    private Logger log = LoggerFactory.getLogger(TestController.class);

    @GetMapping("/test")
    @Transactional
    public AjaxResult<?> add(){
        try {
            // 1、业务处理

            // 2、具体抛出某个错误
            if (true){
               throw new RuntimeException("该用户缺少手机号");
            }

            // 3、业务处理

        }catch (RuntimeException e){
            // 日志记录
            log.error(e.getMessage(),e);
            // 回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            if (e.getMessage().equals("该用户缺少手机号")){
                return AjaxResult.failure("该用户缺少手机号");
            }
            return AjaxResult.failure("系统异常");
        }
        return AjaxResult.failure("新增成功");
    }
}

可以看到,仅仅是一个特殊处理,就已经这么麻烦了,如果是多个的话复杂度更大。


二、全局异常处理

全局异常处理的意思就是:我们把抛出的某种异常统一处理了。

比如我抛出运行时异常,我需要做如下操作

  • 日志记录
  • 返回系统异常给前台

其实所有的异常我们都是要做两个操作,1、日志记录,2、返回错误信息给前台

2-1、全局异常处理器

我们知道所有的异常都属于Exception,我们可以直接对Exception进行处理,然后再在里面进行特殊处理

import com.mysql.cj.jdbc.exceptions.CommunicationsException;
import com.mysql.cj.jdbc.exceptions.MysqlDataTruncation;
import com.xdx97.mianshiba.common.bean.AjaxResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.websocket.SessionException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.sql.SQLTransientConnectionException;

/**
 * 异常处理器
 */
@RestControllerAdvice
public class EceptionHandle {

    private Logger log = LoggerFactory.getLogger(EceptionHandle.class);
    
    @ExceptionHandler(value = Exception.class)
    public AjaxResult<?> handle(Exception e) {
        log.error("已捕捉到异常!");
        AjaxResult<?> bean = new AjaxResult<>();
        Throwable ex = e;
        while(ex.getCause()!=null){
            ex = ex.getCause();
        }
        log.error("error info:" + getTrace(e));
        if (ex instanceof SessionException) {
            bean.setCode(-1);
            SessionException sessionException = (SessionException) ex;
            bean.setErrDesc(sessionException.getMessage());
            return bean;
        }else if (ex instanceof CommunicationsException ||ex instanceof SQLTransientConnectionException || ex instanceof UnknownHostException) { //数据库连接异常
            bean.setCode(-1);
            bean.setErrDesc("目前系统网络较差,数据连接失败!");
            return bean;
        }else if (ex instanceof SocketTimeoutException) { //网络连接超时
            bean.setCode(-1);
            bean.setErrDesc("网络连接超时,请刷新数据!");
            return bean;
        }else if (ex instanceof SocketException) {
            bean.setCode(-1);
            bean.setErrDesc("网络数据丢失,请刷新数据!");
            return bean;
        }else if (ex instanceof MysqlDataTruncation) {
            bean.setCode(-1);
            bean.setErrDesc("字段数据长度超过限制!");
            return bean;
        }else if (ex instanceof RuntimeException) {
            bean.setCode(-1);
            bean.setErrDesc(ex.getMessage());
            return bean;
        }else {
            log.error("【系统异常】", ex);
            bean.setCode(-1);
            bean.setErrDesc("系统错误,请联系管理员");
            return bean;
        }
    }
    /**
     * @desc 获取异常信息
     */
    public String getTrace(Throwable t) {
        StringWriter stringWriter= new StringWriter();
        PrintWriter writer= new PrintWriter(stringWriter);
        t.printStackTrace(writer);
        StringBuffer buffer= stringWriter.getBuffer();
        return buffer.toString();
    }
}



2-2、改造上面的需求

@RestController
public class TestController {

    @GetMapping("/test")
    @Transactional
    public AjaxResult<?> add(){

        // 1、业务处理

        // 2、具体抛出某个错误
        if (true){
           throw new RuntimeException("该用户缺少手机号");
        }

        // 3、业务处理
        return AjaxResult.failure("新增成功");
    }
}

可以看到代码简洁了许多,如果我们再有一个特殊处理,我们只需要继续抛一个就好了。

当系统抛出异常,我们的事务会自动回滚。


自定义异常的两个重要注解 @RestControllerAdvice@ExceptionHandler



三、其它

3-1、如果单个异常处理会怎么样呢?

我们在异常处理器里面新增一个运行时异常处理

@ExceptionHandler(value = RuntimeException.class)
public AjaxResult<?> runtimeException(RuntimeException e) {
    log.error("运行时异常捕获");
    log.error("error info:" + getTrace(e));
    AjaxResult<?> bean = new AjaxResult<>();
    bean.setCode(-1);
    bean.setErrDesc(e.getMessage());
    return bean;
}

因为我们抛出的异常就是RuntimeException,所以它会优先被运行时异常处理器处理


3-2、自定义异常

自定义异常也会优先匹配完全符合的异常。

比如我自定义一个 BizException(继承 RuntimeException),那么捕获它的顺序是(每次只能被捕获一次)

  • @ExceptionHandler(value = BizException.class)
  • @ExceptionHandler(value = RuntimeException.class)
  • @ExceptionHandler(value = Exception.class)