【问题标题】:Spring MVC + Hibernate: data validation strategiesSpring MVC + Hibernate:数据验证策略
【发布时间】:2012-09-21 12:49:04
【问题描述】:

我们都知道,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;
}

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

【问题讨论】:

    标签: java validation spring-mvc hibernate-validator


    【解决方案1】:

    是的,Exception throwing 的 Java 模式已经确立了良好的旧模式。
    Spring MVC 集成的很好(代码示例可以直接跳到我回答的第二部分)。

    您所说的“复杂验证”实际上是例外:业务密钥唯一性错误、低层或数据库错误等。


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

    验证应该发生在表示层上。它基本上是关于验证提交的表单字段。

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

    1) 轻度验证(使用 JSR-303/Hibernate 验证):检查提交的字段是否具有给定的 @Size/@Length,它是 @NotNull@NotEmpty /@NotBlank,检查它是否具有@Email 格式等。

    2) 重度验证或复杂验证更多的是关于字段验证的特定情况,例如跨字段验证:

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

    可以使用Spring Validator implementationscustom annotations/constraints 来实现这些验证。

    现在我明白了,有了所有这些验证工具,再加上 Spring 完全没有侵入性并且可以让你做任何你想做的事情(无论好坏),人们可能会想使用“验证锤”来任何与错误处理模糊相关的内容。
    它会起作用:仅通过验证,您可以检查验证器/注释中的所有可能问题(并且几乎不会在较低层抛出任何异常)。这很糟糕,因为你祈祷你考虑过所有的情况。您不会利用 Java 异常来简化逻辑并通过忘记检查是否有错误来减少出错的机会。

    所以在 Spring MVC 世界中,不应该将验证(即 UI 验证)误认为是低层 异常,例如有 Service 异常或 DB 异常(关键唯一性等)。


    如何在Spring MVC中轻松处理异常?

    有些人认为“天哪,所以在我的控制器中,我必须一一检查所有可能的已检查异常,并为每个异常考虑一个消息错误?没办法!”。我是那些人的其中一个。 :-)

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

    代码示例:

    public class MyAppTechnicalException extends Exception { ... }
    

    @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";
        }
    
    }
    

    简单、快速、简单、干净!

    当您需要为某些特定异常显示错误消息时,或者由于无法修改的旧系统设计不佳而无法提供通用顶级异常时,只需添加其他 @ExceptionHandlers。
    另一个技巧:对于不那么混乱的代码,您可以使用

    处理多个异常
    @ExceptionHandler({MyException1.class, MyException2.class, ...})
    public String yourMethod(Exception e, Model model) {
        ...
    }
    

    底线:何时使用验证?什么时候使用异常?

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

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

    参考资料:

    【讨论】:

    • 杰罗姆,感谢您抽出宝贵时间。请查看更新的问题,我添加了一些细节。您能对此发表意见吗?
    • 我已阅读您的更新,它不会改变我回答的基本内容。使用@ExceptionHandler,您不会分离验证过程,因为异常处理不是验证。在一个好的应用程序中,我认为你应该处理验证和异常:它们在技术上是两个不同的东西。所以没有分离,事情也不复杂。现在,如果您碰巧在验证处理和异常处理中进行了相同的处理,我建议您编写一个方法,例如“processError(...)”,这两个部分都会调用。希望这会有所帮助,我想我不能说更多
    • 不过,如果它们以相同的结尾(向用户显示验证消息)和来自相同的开头(用户输入无效数据),我无法理解它们在“技术上是两个不同的东西” )?假设我没有一个方法,但是有 两个 方法可以抛出这个 ValidationException (一个是保存,另一个 - 删除,删除可能由于非系统错误而失败)并且它们都返回不同的结构JSON。我如何辨别在@ExceptionHandler 中返回哪个视图?
    • 它们是两个不同的东西,因为它们不是验证:数据库错误,因为基础已满,数据库中不存在要更新的元素,您的控制器所依赖的 Web 服务离线或有问题,服务无法创建文件,因为文件夹上的权限已被更改,以及我现在没有想到的任何其他异常:对于大多数情况,您想警告用户“发生了一些意外错误” .至于您的示例,您可以使用 SaveException 和 DeleteException 来实现差异,您可以使用两个不同的 @ExceptionHandler 来处理它们。
    • 简单示例——业务密钥唯一性检查,不是“base is full”之类的系统错误,但与@NotNullcheck still大致相同,它需要复杂像 DB 一样处理。你会把这张支票放在哪里?如果在@ExceptionHandler 中,那么我无法理解为什么将其与BindingResult 支票分开。开发人员希望找到组合(或至少链接)在一起的所有验证步骤。如果控制器很大,将它们放入单独的方法中需要额外的脑力劳动(例如,“验证消息来自哪里?!不是来自 BindingResult!”)。
    猜你喜欢
    • 2011-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多