SpringBoot数据校验+全局异常处理--@RequestBody与@Valid一起使用校验失效

本文介绍了在SpringBoot中如何使用spring-boot-starter-validation模块进行数据校验,并结合全局异常处理类GlobalExceptionHandler捕获和处理异常。当使用@RequestBody+@Valid注解时,遇到的数据校验异常是MethodArgumentNotValidException,而单独使用@Valid时则是BindException。解决方案是在全局异常处理类中添加对应的方法来捕获不同类型的异常,确保数据校验异常能被正确处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

场景:

SpringBoot 通过 spring-boot-starter-validation 模块包含了数据校验的⼯作,只需要简单几个注解就可以对请求数据进行校验,很方便。这过程还包含了全局异常的处理,以下是步骤:

1、User 实体类属性添加校验注解

public class User{
 
 private Integer id;
 //校验注解
 @NotBlank(message = "⽤户名不能为空!")
 private String userName;
 @NotBlank(message = "⽤户密码不能为空!")
 @Length(min = 6, max = 10,message = "密码⻓度⾄少6位但不超过10位!")
 private String userPwd;
 
 /*
 省略get set ⽅法 
 */
}

2、在控制层的接口方法形参上添加@RequestBody+@Valid 注解

@RestController
public class UserController {
    @Autowired
    private UserService userService;
/**
     * 添加用户
     * @param user
     * @return
     */
@PostMapping("user")
    public ResultInfo addUser(@RequestBody @Valid User user)
    {

        //结果集
        ResultInfo resultInfo = new ResultInfo();
        //有了全局异常处理GlobalExceptionHandler类,可以不用加try catch
        userService.addUser(user);

        //没有异常则返回默认的结果值:200,添加成功
        return resultInfo;
    }
   }

3、在全局异常处理类中添加校验异常方法BindExceptionHandler

/**
 * 全局异常处理
 * 返回JSON格式
 */

//Controller增强器,可对controller中被 @RequestMapping注解的方法加一些逻辑处理,当将异常抛到controller时,可以对异常进行统一处理,规定返回的json格式或是跳转到一个错误页面
@ControllerAdvice
public class GlobalExceptionHandler {
/**
     * 数据校验全局处理
     * BindException是@Valid使用校验失败时产生的异常
     */
    @ExceptionHandler(value = BindException.class)
    @ResponseBody
    public ResultInfo BindExceptionHandler(BindException e)
    {

        //捕获数据校验异常
        ResultInfo resultInfo = new ResultInfo();
        resultInfo.setCode(500);
        //获取实体类定义的校验注解字段上的message作为异常信息,@NotBlank(message = "用户密码不能为空!")异常信息即为"用户密码不能为空!"
        resultInfo.setResultmsg(e.getBindingResult().getFieldError().getDefaultMessage());
        return resultInfo;
    }
     /**
     * 全局异常处理
     *如果设置了特定异常处理,全局异常处理可作为兜底异常
     * @param e
     * @return
     */
    //设置处理的异常类型,如果没有特定异常则为最大的异常Exception.class
    @ExceptionHandler(value = Exception.class)
    //最终结果是JSON格式
    @ResponseBody
    public ResultInfo exceptionHandler(Exception e)
    {

        ResultInfo resultInfo = new ResultInfo();
        //一旦进入这个方法说明有异常,这是固定的异常信息
        resultInfo.setCode(100);
        resultInfo.setResultmsg("操作异常!");
        return resultInfo;
    }
}

问题描述

使用postman测试结果,userPwd密码故意设置成不符合规范的密码,但是测试捕获异常的不是校验异常而是全局异常
在这里插入图片描述
而在控制台中却把校验异常的错误打印出来了

2022-03-13 13:27:26.701  WARN 14232 --- [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.xxxx.springboot_mybatis.po.vo.ResultInfo com.xxxx.springboot_mybatis.controller.UserController.addUser(com.xxxx.springboot_mybatis.po.User): 
[Field error in object 'user' on field 'userPwd': rejected value [222]; codes [Length.user.userPwd,Length.userPwd,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.userPwd,userPwd]; arguments []; default message [userPwd],10,6]; default message [密码⻓度⾄少6位但不超过10!]] ]

我又单独使用了注解@Valid而没有使用@RequestBody

@RestController
public class UserController {
    @Autowired
    private UserService userService;
/**
     * 添加用户
     * @param user
     * @return
     */
@PostMapping("user")
    public ResultInfo addUser(@Valid User user)
    {

        //结果集
        ResultInfo resultInfo = new ResultInfo();
        //有了全局异常处理GlobalExceptionHandler类,可以不用加try catch
        userService.addUser(user);

        //没有异常则返回默认的结果值:200,添加成功
        return resultInfo;
    }
   }

还是同样的测试数据,这次的测试结果如下:
数据校验异常捕获成功
在这里插入图片描述
测试结果和控制台打印异常一致

2022-03-13 13:40:31.560  WARN 11844 --- [nio-8080-exec-5] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors

Field error in object 'user' on field 'userPwd': rejected value [222]; codes [Length.user.userPwd,Length.userPwd,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.userPwd,userPwd]; arguments []; default message [userPwd],10,6]; default message [密码⻓度⾄少6位但不超过10!]]

原因分析:

对比一下两次的测试结果以及控制台打印异常,发现了一个不同的地方,控制台显示使用@RequestBody+@Valid注解产生的异常是MethodArgumentNotValidException,而单独使用注解@Valid产生的异常是BindException。


解决方案:

提示:在全局异常处理类中再添加一个新的方法,用于捕获控制台打印的异常MethodArgumentNotValidException

/**
     * 数据校验全局处理
     * MethodArgumentNotValidException是@RequestBody和@Validated配合时产生的异常,比如在传参时如果前端的json数据里部分缺失@RequestBody修饰的实体类的属性就会产生这个异常。
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseBody
    public ResultInfo MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e)
    {

        //捕获数据校验异常
        ResultInfo resultInfo = new ResultInfo();
        resultInfo.setCode(600);
        //获取实体类定义的校验注解字段上的message作为异常信息,@NotBlank(message = "用户密码不能为空!")异常信息即为"用户密码不能为空!"
        resultInfo.setResultmsg(e.getBindingResult().getFieldError().getDefaultMessage());
        return resultInfo;
    }

在控制层的接口方法形参上添加@RequestBody+@Valid 注解再测试一遍,数据校验异常捕获成功
在这里插入图片描述
控制台同样打印出了MethodArgumentNotValidException异常

2022-03-13 13:53:42.574  WARN 11732 --- [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.xxxx.springboot_mybatis.po.vo.ResultInfo com.xxxx.springboot_mybatis.controller.UserController.addUser(com.xxxx.springboot_mybatis.po.User): 
[Field error in object 'user' on field 'userPwd': rejected value [222]; codes [Length.user.userPwd,Length.userPwd,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.userPwd,userPwd]; arguments []; default message [userPwd],10,6]; default message [密码⻓度⾄少6位但不超过10!]] ]

完结撒花~~~

<think>首先,用户的问题是:“我想了解如何使用自定义注解@valid并结合数据库知识库进行内容校验请问如何创建和使用自定义注解@Valid以从数据库中获取知识库并校验内容javaspring”关键点:-用户想使用自定义注解`@Valid`(注意,在Spring中,`@Valid`是标准注解,用户可能指的是创建自定义验证注解)。-结合数据库知识库进行内容校验-JavaSpring环境中。从系统指令:-所有行内数学表达式用`$...$`,独立公式用`$$...$$`。-LaTeX语法正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾添加引用标识,如`[^1]`。-结构清晰,逐步解决问题。-参考提供的引用,但这些引用是关于`@Valid`的,不是直接相关数据校验。分析用户需求:-用户可能误解了`@Valid`;`@Valid`是JSR-303/JSR-349标准注解,用于触发验证。自定义验证通常通过创建自定义约束注解和验证器。-用户想从数据库中获取知识库(如数据字典)来校验内容。例如,校验输入是否在数据库的某个列表中。-Spring中,这可以通过自定义验证器实现,该验证器访问数据库。步骤:1.**创建自定义注解**:定义一个注解,如`@DatabaseConstraint`。2.**实现验证器**:创建一个类实现`ConstraintValidator`,在其中访问数据库。3.**在实体类上使用注解**:将自定义注解应用到字段上。4.**触发验证**:在Controller中使用`@Valid`触发验证。参考引用:-引用[3]提到了自定义注解:`@Constraint(validatedBy={MyPhoneValidtor.class})`,定义`@MyPhone`注解。-引用[4]展示了基本校验,但没有数据库部分。我需要构建一个完整的示例:-假设场景:校验用户名是否在数据库的用户表中存在。-使用SpringDataJPA或JDBC访问数据库。-确保回答真实可靠。回答结构:1.**介绍**:解释自定义验证和数据校验的概念。2.**创建自定义注解**:代码示例。3.**实现验证器**:代码示例,包括数据库访问。4.**在实体类上使用**:示例。5.**在Controller中使用**:示例。6.**注意事项**:如事务管理、性能。7.**生成相关问题**。在回答中:-使用中文。-添加引用标识,如果引用内容。-数学表达式:可能不需要,但如果有,如公式,用LaTeX格式。-相关问题:基于用户问题生成。最终,生成相关问题。</think>### 如何使用自定义注解结合数据库知识库进行内容校验Java Spring) 在Java Spring应用中,使用自定义注解结合数据库知识库进行内容校验,是一种高效的数据验证方法。标准注解 `@Valid` 用于触发校验流程,但用户需**自定义约束注解**和**验证器**来实现数据库驱动的校验。例如,校验用户输入是否存在于数据库的“知识库”(如数据字典表)中。下面我将一步步解释实现过程,确保结构清晰、真实可靠。整个过程基于Spring Boot(版本≥2.x),使用Spring Data JPA访问数据库,并依赖Hibernate Validator。 --- #### 1. **准备依赖和环境** 首先,确保项目中包含必要的依赖(Spring Boot已默认包含 `spring-boot-starter-web`,它集成了 `validation-api` 和 `hibernate-validator`)。如果非Spring Boot项目,需手动添加(参考引用[1][^1]和[2][^2]): ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <!-- 示例用内存数据库,可替换为MySQL等 --> <scope>runtime</scope> </dependency> ``` #### 2. **创建自定义注解** 自定义注解用于标记需要校验的字段,并指定验证器。例如,定义一个 `@InDatabase` 注解,校验字段值是否在数据库知识库中存在: ```java import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; @Documented @Constraint(validatedBy = DatabaseValidator.class) // 指定验证器类 @Target({ElementType.FIELD}) // 应用于字段 @Retention(RetentionPolicy.RUNTIME) public @interface InDatabase { String message() default "值不在知识库中"; // 默认错误消息 Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; String tableName(); // 知识库表名,从注解参数获取 String columnName(); // 知识库列名 } ``` - **关键点**:`tableName` 和 `columnName` 参数允许动态指定数据库表,实现知识库的灵活查询。 #### 3. **实现自定义验证器** 创建验证器类 `DatabaseValidator`,实现 `ConstraintValidator` 接口。在其中注入数据库访问服务(如JPA Repository),执行查询校验: ```java import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class DatabaseValidator implements ConstraintValidator<InDatabase, String> { @Autowired private KnowledgeRepository knowledgeRepository; // 自定义的JPA Repository private String tableName; private String columnName; @Override public void initialize(InDatabase constraintAnnotation) { this.tableName = constraintAnnotation.tableName(); this.columnName = constraintAnnotation.columnName(); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) { return true; // 空值跳过校验,由 @NotNull 等处理 } // 从数据库知识库查询:检查值是否存在于指定表和列 return knowledgeRepository.existsByTableAndColumn(tableName, columnName, value); } } ``` - **数据库访问**:`KnowledgeRepository` 是自定义的Spring Data JPA接口。示例代码如下: ```java import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; public interface KnowledgeRepository extends JpaRepository<KnowledgeEntity, Long> { @Query("SELECT COUNT(e) > 0 FROM #{#entityName} e WHERE e.tableName = :table AND e.columnName = :column AND e.value = :value") boolean existsByTableAndColumn(@Param("table") String table, @Param("column") String column, @Param("value") String value); } ``` - **实体类示例**(知识库表结构): ```java import javax.persistence.Entity; import javax.persistence.Id; @Entity public class KnowledgeEntity { @Id private Long id; private String tableName; // 如 "user_table" private String columnName; // 如 "username" private String value; // 如 "admin" // Getters and setters } ``` - **性能注意**:频繁查询数据库可能影响性能。建议添加缓存(如Spring Cache)或批量查询优化[^4]。 #### 4. **在实体类上应用自定义注解** 在需要校验的DTO或实体类字段上使用 `@InDatabase` 注解,指定知识库表和列: ```java public class UserInput { @NotBlank(message = "用户名不能为空") @InDatabase(tableName = "user_table", columnName = "username", message = "用户名不在知识库中") private String username; // 其他字段... // Getters and setters } ``` #### 5. **在Controller中触发校验** 在Controller方法参数上使用 `@Valid` 注解,触发校验流程。校验失败时,Spring会自动返回错误信息: ```java import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; @RestController public class UserController { @PostMapping("/validate") public String validateInput(@Valid @RequestBody UserInput userInput) { // 校验通过后执行业务逻辑 return "校验成功"; } } ``` - **错误处理**:Spring默认返回400错误和详细消息。可通过 `@ControllerAdvice` 全局异常处理器自定义响应[^3]。 #### 6. **注意事项** - **事务管理**:验证器中的数据库查询需在事务内运行。确保Spring配置启用事务管理(`@EnableTransactionManagement`)。 - **依赖注入**:验证器必须是Spring Bean(使用 `@Component`),否则无法注入Repository。 - **知识库更新**:如果知识库频繁变更,需实现缓存失效机制(如Redis)。 - **安全**:避免SQL注入,使用JPA参数化查询。 通过以上步骤,您可以实现基于数据库知识库的动态内容校验。结合自定义注解和Spring验证框架,代码简洁且易于扩展[^1][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lynn Lu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值