Spring MVC + Hibernate:数据validation策略

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

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

更新:让我更具体一点。 例如,有一个表单可以向控制器的save方法发送AJAX保存请求。 如果发生一些validation错误 – 简单或“复杂” – 我们应该回到浏览器,其中一些json指示有问题的字段和相关错误。 对于简单错误,我可以从BindingResult提取字段(如果有)和错误消息。 您会针对“复杂”错误提出什么样的基础设施(可能是特定的,而不是临时的例外?)? 使用exception处理程序对我来说似乎不是一个好主意,因为在save方法和@ExceptionHandler之间分离单个validation过程会使事情变得复杂。 目前我使用一些临时exception(如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模式的Exception throw
Spring MVC很好地集成了它(对于代码示例,您可以直接跳到我的答案的第二部分)。

您称之为“复杂validation”的事实上是例外:业务密钥单一性错误,低层或DB错误等。


提醒:Spring MVC中的validation是什么?

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

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

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

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

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

可以使用Spring Validator实现或自定义注释/约束来实现这些validation。

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

因此,在Spring MVC世界中,不应该对较低层exception进行validation(即UIvalidation ),例如服务exception或数据库exception(密钥唯一性等)。


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

有些人认为“哦,上帝,所以在我的控制器中,我必须逐个检查所有可能的检查exception,并考虑每个人的消息错误?没有办法!”。 我是那些人的其中一个。 🙂

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

代码示例:

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

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

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

 @ExceptionHandler({MyException1.class, MyException2.class, ...}) public String yourMethod(Exception e, Model model) { ... } 

底线:何时使用validation? 何时使用例外?

  • 来自UI =validation=validation工具的错误(JSR-303注释,自定义注释,Springvalidation器)
  • 较低层的错误=exception

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

参考文献:

  • 将错误从服务层传递回视图
  • 关于beanvalidation的非常翔实的博客文章