Spring MVC + Hibernate:数据验证策略

Posted

技术标签:

【中文标题】Spring MVC + Hibernate:数据验证策略【英文标题】:Spring MVC + Hibernate: data validation strategies 【发布时间】: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;

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

【问题讨论】:

【参考方案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 implementations 或custom 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 的错误”时,我的意思是“用户在他的表单中输入了错误”。

参考资料:

Passing errors back to the view from the service layer Very informative blog post about bean validation

【讨论】:

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

以上是关于Spring MVC + Hibernate:数据验证策略的主要内容,如果未能解决你的问题,请参考以下文章

Spring+Spring MVC+Hibernate框架搭建实例

spring mvc+spring + hibernate 整合

如何使用 Hibernate、Spring MVC 将数据从单个表单发送到多个数据库表

在spring mvc中选择spring hibernate中的查询

Spring MVC 设计概述

使用 Spring Boot + Hibernate + MySql 运行 MVC 应用程序