Spring MVC + Hibernate:数据验证策略

And*_*uta 22 java validation spring-mvc hibernate-validator

我们都知道,Spring MVC通常与Hibernate Validator和JSR-303很好地集成.但正如有人所说,Hibernate Validator只适用于Bean Validation,这意味着应该将更复杂的验证推送到数据层.此类验证的示例:业务密钥唯一性,内部记录依赖性(通常指向数据库设计问题,但我们都生活在一个不完美的世界).即使像字符串字段长度这样的简单验证也可能由某个DB值驱动,这使得Hibernate Validator无法使用.

所以我的问题是,Spring或Hibernate或JSR是否提供了执行此类复杂验证的功能?是否有一些已建立的模式或技术部分可以在基于Spring和Hibernate的标准Controller-Service-Repository设置中执行此类验证?

更新:让我更具体一点.例如,有一个表单向控制器的save方法发送AJAX保存请求.如果出现一些验证错误 - 简单或"复杂" - 我们应该回到浏览器,其中一些json指示有问题的字段和相关错误.对于简单错误,我可以从中提取字段(如果有)和错误消息BindingResult.您会针对"复杂"错误提出什么样的基础设施(可能是特定的,而不是临时的例外?)?使用异常处理程序对我来说似乎不是一个好主意,因为在save方法之间分离单个验证过程@ExceptionHandler并使事情变得复杂.目前我使用一些特殊的例外(如,ValidationException):

public @ResponseBody Result save(@Valid Entity entity, BindingResult errors) {
    Result r = new Result();
    if (errors.hasErrors()) {
        r.setStatus(Result.VALIDATION_ERROR);     
        // ...   
    } else {
        try {
            dao.save(entity);
            r.setStatus(Result.SUCCESS);
        } except (ValidationException e) {
            r.setStatus(Result.VALIDATION_ERROR);
            r.setText(e.getMessage());
        }
    }
    return r;
}
Run Code Online (Sandbox Code Playgroud)

你能提供更优化的方法吗?

Jer*_*ert 41

是的,有一个很好的旧的Java模式的Exception抛出.
Spring MVC很好地集成了它(对于代码示例,您可以直接跳到我的答案的第二部分).

你称之为"复杂验证"的事实上是例外:业务密钥单一性错误,低层或DB错误等.


提醒:Spring MVC中的验证是什么?

验证应该在表示层上进行.它基本上是关于验证提交的表单字段.

我们可以将它们分为两类:

1)光验证(有JSR-303 /休眠验证):检查所提交的字段具有给定@Size/ @Length,它是@NotNull@NotEmpty/ @NotBlank,检查它具有@Email格式等

2)重度验证或复杂验证更多地涉及现场验证的特定情况,例如跨领域验证:

  • 示例1:表单有fieldA,fieldBfieldC.单独地,每个字段可以为空,但其中至少有一个不能为空.
  • 示例2:如果userAge字段的值小于18,则responsibleUser字段不能为空,且responsibleUser年龄必须大于21.

可以使用Spring Validator实现自定义注释/约束来实现这些验证.

现在我明白了所有这些验证设施,再加上Spring根本没有侵入性并允许你做任何你想做的事情(无论好坏),人们可能会试图用"验证锤"来做任何模糊的事情.错误处理.
并且它可以工作:仅通过验证,您可以检查验证器/注释中的每个可能的问题(并且几乎不会在较低层中抛出任何异常).这很糟糕,因为你祈祷你想到了所有的情况.您不会利用允许您简化逻辑的Java异常,并通过忘记检查某些内容是否有错误来减少出错的可能性.

因此,在Spring MVC世界中,不应该将错误验证(也就是说,UI验证)误认为是低层异常,例如服务异常或数据库异常(关键单一性等).


如何以方便的方式处理Spring MVC中的异常?

有些人认为"哦,上帝,所以在我的控制器中,我必须逐个检查所有可能的检查异常,并考虑每个人的消息错误?没有办法!".我是那些人的其中一个.:-)

对于大多数情况,只需使用一些通用的检查异常类,所有异常都会扩展.然后使用@ExceptionHandler和一般错误消息在Spring MVC控制器中处理它.

代码示例:

public class MyAppTechnicalException extends Exception { ... }
Run Code Online (Sandbox Code Playgroud)

@Controller
public class MyController {

    ...

    @RequestMapping(...)
    public void createMyObject(...) throws MyAppTechnicalException {
        ...
        someServiceThanCanThrowMyAppTechnicalException.create(...);
        ...
    }

    ...

    @ExceptionHandler(MyAppTechnicalException.class)
    public String handleMyAppTechnicalException(MyAppTechnicalException e, Model model) {

        // Compute your generic error message/code with e.
        // Or just use a generic error/code, in which case you can remove e from the parameters
        String genericErrorMessage = "Some technical exception has occured blah blah blah" ;

        // There are many other ways to pass an error to the view, but you get the idea
        model.addAttribute("myErrors", genericErrorMessage);

        return "myView";
    }

}
Run Code Online (Sandbox Code Playgroud)

简单,快速,简单,干净!

对于那些需要显示某些特定异常的错误消息的情况,或者由于设计不良的遗留系统而无法修改通用顶级异常时,您只能添加其他异常@ExceptionHandler.
另一个技巧:对于较少杂乱的代码,您可以处理多个异常

@ExceptionHandler({MyException1.class, MyException2.class, ...})
public String yourMethod(Exception e, Model model) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

底线:何时使用验证?何时使用例外?

  • 来自UI =验证=验证工具的错误(JSR-303注释,自定义注释,Spring验证器)
  • 较低层的错误=异常

当我说"来自UI的错误"时,我的意思是"用户在他的表单中输入了错误".

参考文献:

  • 简单的例子 - 业务密钥唯一性检查,而不是像"基本已满"这样的系统错误,但与"@NotNull"检查*或多或少相同*它需要像DB命中这样的复杂处理.你会把这张支票放在哪里?如果在`@ExceptionHandler`中,那么我无法理解为什么将它与`BindingResult`检查分开.开发人员希望将所有验证步骤组合在一起(或至少链接在一起).如果控制器很大(例如,"验证消息来自何处?不是来自BindingResult!"),将它们放入单独的方法中需要额外的心理努力. (3认同)