Springboot 优雅的处理数据库报错信息

本文介绍如何通过Spring Boot的@RestControllerAdvice捕获并解析SQLIntegrityConstraintViolationException,利用反射找到被占用字段及其注解,解决数据库完整性约束冲突问题。实例包括邮箱和账号占用,原理涉及正则表达式和Java反射。
摘要由CSDN通过智能技术生成


Duplicate entry 'xxx' for key 'xxxx'

平时我们遇到 mysql 报错的时候很难处理,例如字段重复了,破坏了完整性约束,但是我们又不可能每个唯一字段都去进行查询,看看是不是重复了,这样相当于写死了代码。

那么如何动态的去知道那个信息被占用,或者重复了呢? 这里就需要用到反射的原理


首先我们得学会捕获全局异常 :

使用 @RestControllerAdvice 环绕增强 ,然后再使用 @ExceptionHandler 注解,在某个方法上面给指定的异常类进行注解, Speingboot 会自动将异常分发给你的方法进行处理。

这里的 @ResponseHandler 注解是统一信息处理注解,可以在我的文章 https://blog.csdn.net/qq_31254489/article/details/119772338 中去学习配置,如果你不想用,可以删掉。

@RestController
@ResponseHandler
@RestControllerAdvice
@Api(tags = "統一错误请求控制")
public class MyErrorController implements ErrorController {
	// 统一sql异常处理类
    @Autowired
    SqlExceptionHandler sqlExceptionHandler;

    // sql 报错处理
    @ExceptionHandler(value = SQLException.class)
    @ResponseStatus
    public String sqlError(SQLException e) {
    	// 我们交给异常处理类去处理
        return sqlExceptionHandler.handle(e);
    }
}

具体流程如下
在这里插入图片描述

完整性约束报错处理

一般就是插入的时候和已经有的数据重复了

接下来复制粘贴下面2个类,并要遵守以下规则

  • 数据库的约束名称必须遵守 表名.表名_键1_键2_键3_xxx_uindex 例如 user.user_name_unidex 意思就是 user 表下的 name 的唯一约束。
  • 实体类的名称,还有属性,必须和数据库中的一致,mysql 必须遵守下划线命名规则, java 则可以使用 驼峰或者下划线。
  • 使用 @HandleSqlException 注解去方法上面标注需要处理的具体异常。 例如 下面的 integrityConstraint 方法。
/**
 * sql 错误异常处理
 *
 * @author enncy
 */
@Component
public class SqlExceptionHandler {
    /**
     *  分发异常处理
     * @return: void
     */
    public String handle(SQLException e) {
    	// 获取当前类的方法
        Optional<Method> first = Arrays.stream(SqlExceptionHandler.class.getDeclaredMethods())
        		// 寻找有 HandleSqlException 注解的方法
                .filter(m -> m.isAnnotationPresent(HandleSqlException.class))
                // 寻找和参数 e 的类相等的 HandleSqlException 
                .filter(m -> m.getAnnotation(HandleSqlException.class).value() == e.getClass())
                .findFirst();

		// 如果存在,则分发给指定的方法
        if( first.isPresent()){
            try {
                return (String) first.get().invoke(this, e);
            } catch (IllegalAccessException | InvocationTargetException exception) {
                return "服务器内部出现异常";
            }
        }else{
            return "服务器内部出现异常";
        }
    }

    /**
     *  完整性约束被破坏处理
     * @return: java.lang.String
     */
    @HandleSqlException(SQLIntegrityConstraintViolationException.class)
    public String integrityConstraint(SQLException e) {
        // 匹配数据库报错信息
        String regex = "Duplicate entry '(.*?)' for key '(.*?)\\..*?_(.*?)_uindex'";
        Matcher matcher = Pattern.compile(regex).matcher(e.getMessage());
        // 如果存在信息
        if (matcher.find()) {
            // 获取被占用的值
            String value = matcher.group(1);
            // 获取数据库表
            String table = matcher.group(2);
            // 获取发生冲突的约束键
            String keys = matcher.group(3);
            // 扫描实体类
            List<Class<?>> scan = ClassScanner.scan("cn.enncy.funny.entity");
            // 寻找冲突的字段的注解描述
            String description = scan.stream()
                    .filter(BaseEntity.class::isAssignableFrom)
                    .filter(c -> c.getName().toLowerCase().contains(table))
                    .map(c -> {
                        // 获取实体类的属性值
                        Field[] declaredFields = c.getDeclaredFields();
                        for (Field declaredField : declaredFields) {
                            // 驼峰转下划线,这里的 humpToUnderline 方法参考文章 : https://blog.csdn.net/qq_31254489/article/details/115842821
                            String name = StringUtils.humpToUnderline(declaredField.getName());
                            // 如果属性包含在 keys 的值里面
                            if (keys.contains(name)) {
                                // 返回属性上面的 注解信息
                                return declaredField.getAnnotation(ApiModelProperty.class).value();
                            }
                        }
                        return "";
                    }).findFirst().orElse("");

            return description + " " + value + " 已经被占用";
        }
        return "信息已经被占用";
    }
}
/**
 * @author enncy
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HandleSqlException {
    Class<? extends SQLException> value();
}

然后修改完整性约束处理的里面的包路径 cn.enncy.funny.entity , 这个需要你改到自己项目的实体类包下,也就是POJO类所在的包。


测试实例

这是我的实体类 , 在 ‘cn.enncy.xxx.entity’ 包下,所以把包扫描信息改成 ClassScanner.scan("cn.enncy.xxx.entity");
在这里插入图片描述
然后把项目跑起来,测试占用信息。

邮箱字段被占用

报错 : Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'xxxxxxxxxx@qq.com' for key 'user.user_email_uindex'
在这里插入图片描述
响应
在这里插入图片描述

账号被占用

报错 : Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'xxxxx' for key 'user.user_account_uindex'
在这里插入图片描述


原理

大概的就是利用 mysql 报错的信息, 然后进行字符串处理。
思路 :

  • 全局获取 mysql 的报错信息
  • 对报错信息进行正则表达式判断,取出有用的信息。
  • 利用反射原理找出和报错信息所对应的实体类
  • 反射找出实体类的字段和注释信息
  • 最后返回统一信息
根据提供的引用内容,springboot配置接口文档报错的问题可以通过以下两种方式解决。 首先,在es的配置类中使用@PostConstruct注解进行初始化设置。具体代码如下: ``` @Configuration public class ElasticSearchConfig { @PostConstruct void init() { System.setProperty("es.set.netty.runtime.available.processors", "false"); } @Bean public TransportClient esClient() throws UnknownHostException { Settings settings = Settings.builder() .put("cluster.name", "xunwu") .put("client.transport.sniff", true) .build(); @SuppressWarnings("resource") TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new TransportAddress(InetAddress.getByName("192.168.111.132"), 9300)); return client; } } ``` 其次,如果第一种方式还是不行,可以在启动main函数中设置系统属性。具体代码如下: ``` @SpringBootApplication public class App { public static void main(String[] args) { System.setProperty("es.set.netty.runtime.available.processors", "false"); SpringApplication.run(App.class, args); } } ``` 以上两种方法都可以解决springboot配置接口文档报错的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [springboot整合es启动报错的问题](https://blog.csdn.net/zhangcongyi420/article/details/88617660)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [springBoot集成Swagger报错处理](https://blog.csdn.net/King_3123/article/details/125651813)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值