Spring MVC+Hibernate:数据验证策略



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

所以我的问题是,Spring、Hibernate或JSR是否提供了一些东西来执行如此复杂的验证?在基于Spring和Hibernate的标准控制器服务存储库设置中,是否有一些已建立的模式或技术来执行此类验证?

更新:让我更具体一点。例如,有一个表单向控制器的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模式异常抛出
Spring MVC很好地集成了它(对于代码示例,您可以直接跳到我答案的第二部分)。

你称之为";复杂验证";实际上是例外:业务密钥单一性错误、低层或DB错误等。


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

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

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

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

2)重验证或复杂验证更多地是关于现场验证的特定情况,例如跨现场验证:

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

这些验证可以通过Spring Validator实现或自定义注释/约束来实现。

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

因此,在Spring MVC世界中,不应将验证(也就是说,UI验证)错误地用于下层异常,例如Service异常或DB异常(密钥唯一性等)


如何在SpringMVC中以方便的方式处理异常

有些人认为;哦,天哪,所以在我的控制器中,我必须逐一检查所有可能的已检查异常,并考虑每个异常的消息错误?没门&";。我就是其中之一。:-)

对于大多数情况,只需使用一些通用的已检查异常类,所有异常都将扩展该类。然后简单地在您的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=validation=validation工具的错误(JSR-303注释、自定义注释、Spring验证器)
  • 来自下层的错误=异常

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

参考文献:

  • 将错误从服务层传递回视图
  • 关于bean验证的内容丰富的博客文章

最新更新