Java使用自定义注解优雅地解决异常
前言
我们在实际的开发的过程中是不是经常会遇到这样的情况:当调用服务出现错误的时候页面直接报500的错误,并且在页面显示一大串错误提示,很明显这种异常信息的展示对用户体验是非常不好的,用户更关心服务能不能用,并不在乎问题的原因,那么开发的过程中如何去处理这些异常呢?
正文
首先我们要知道 Throwable
类是Java
异常类型的顶层父类,它有两个子类
Error
: 用于指示合理的应用程序不应该试图捕获的严重问题.
Exception
:可以被捕获的问题,并可以细分为以下两种
- IOException(I/O 输入输出异常):其中
IOException
及其子类异常又被称作「受查异常」 - RuntimeException(运行时异常):
RuntimeException
被称作「非受查异常」
受查异常不处理在编译时就会报错,非受查异常不处理只会在运行时报错。
我们这里着重关注RuntimeException
相关的异常,并且在真实的开发过程中我们最常见的就是数据访问相关的运行时异常。
所以简单列举一SpringWeb常见的数据访问异常:
- CleanupFailureDataAccessException:一项操作成功地执行,但在释放数据库资源时发生异常(例如,关闭一个
Connection
) - DataAccessResourceFailureException: 数据访问资源彻底失败,例如不能连接数据库
- DataIntegrityViolationException:
Insert
或Update
数据时违反了完整性,例如违反了惟一性限制 - DataRetrievalFailureException:某些数据不能被检测到,例如不能通过关键字找到一条记录
- DeadlockLoserDataAccessException: 当前的操作因为死锁而失败
- IncorrectUpdateSemanticsDataAccessException:
Update
时发生某些没有预料到的情况,例如更改超过预期的记录数。当这个异常被抛出时,执行着的事务不会被回滚 - InvalidDataAccessApiusageException:一个数据访问的
JAVA API
没有正确使用,例如必须在执行前编译好的查询编译失败了 - InvalidDataAccessResourceUsageException:错误使用数据访问资源,例如用错误的
SQL
语法访问关系型数据库 - OptimisticLockingFailureException: 乐观锁的失败。这将由
ORM
工具或用户的DAO
实现抛出 - TypemismatchDataAccessException:Java类型和数据类型不匹配,例如试图把
String
类型插入到数据库的数值型字段中 - UncategorizedDataAccessException: 有错误发生,但无法归类到某一更为具体的异常中
我们这里以DataIntegrityViolationException
为例,因为在实际的开发过程中我们会遇到验证主键唯一性而去查一次数据库的场景,我们通过这种自定义异常的方式,可以减少一次数据库查询,进而提升系统新增数据的性能。
使用自定义异常进行主键冲突校验
BusinessException.class:自定义业务异常
/**
* 业务自定义异常
*/
@Getter
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = -1895174013651345407L;
private final CouponTypeEnum errorCode;
private String primaryErrorCode;
private String primaryErrorMsg;
private String primaryErrorIP;
public BusinessException(CouponTypeEnum errorCode) {
this(errorCode, errorCode.getCouponTypeDesc());
}
public BusinessException(CouponTypeEnum errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public BusinessException(CouponTypeEnum errorCode, String message,String primaryErrorCode,String primaryErrorMsg,String primaryErrorIP) {
super(message);
this.errorCode = errorCode;
this.primaryErrorCode=primaryErrorCode;
this.primaryErrorMsg=primaryErrorMsg;
this.primaryErrorIP=primaryErrorIP;
}
public BusinessException(CouponTypeEnum errorCode,String primaryErrorCode,String primaryErrorMsg,String primaryErrorIP) {
this(errorCode, errorCode.getCouponTypeDesc());
this.primaryErrorCode=primaryErrorCode;
this.primaryErrorMsg=primaryErrorMsg;
this.primaryErrorIP=primaryErrorIP;
}
}
CouponTypeEnum 业务提示语枚举类
@Getter
public enum CouponTypeEnum {
USER_ALREADY_EXISTS (23,"用户已存在"),
NEW_USER_FAILED(24,"新增用户失败");
/**
* 状态值
*/
private int couponType;
/**
* 状态描述
*/
private String couponTypeDesc;
CouponTypeEnum(int couponType, String couponTypeDesc){
this.couponType = couponType;
this.couponTypeDesc = couponTypeDesc;
}
public static String getDescByType(int couponType) {
for (CouponTypeEnum type : CouponTypeEnum.values()) {
if (type.couponType == couponType) {
return type.couponTypeDesc;
}
}
return null;
}
}
测试
测试接口
@PostMapping("/helloluo")
public String helloluo(UserPojoReq userPojoReq){
try {
userService.addUser(userPojoReq);
}catch (DataIntegrityViolationException e){
throw new BusinessException(CouponTypeEnum.USER_ALREADY_EXISTS);
}catch (Exception e){
throw new BusinessException(CouponTypeEnum.NEW_USER_FAILED);
}
return "Hello World";
}
故意去插入一个主键已被使用的用户实例
返回结果
源码
项目源码可从的我的github中获取:github源码地址