Spring MVC + Hibernate:数据验证策略
问题内容:
众所周知,Spring MVC通常可以与Hibernate Validator和JSR-303很好地集成。但是正如有人所说,Hibernate
Validator仅用于Bean验证,这意味着应该将更复杂的验证推送到数据层。此类验证的示例:业务密钥唯一性,内部记录依赖关系(通常指出数据库设计问题,但我们都生活在一个不完善的世界中)。甚至一些简单的验证(例如字符串字段长度)也可能由某些DB值驱动,这使Hibernate
Validator无法使用。
所以我的问题是,Spring,Hibernate或JSR是否提供了执行这种复杂验证的功能?是否有一些 成熟的
模式或技术可以在基于Spring和Hibernate的标准Controller-Service-Repository设置中执行这种验证?
更新:
让我更具体。例如,有一种表单将AJAX保存请求发送到控制器的save
方法。如果发生某些验证错误(简单或“复杂”),我们应该使用一些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模式 Exception throw 。
Spring MVC很好地集成了它(对于代码示例,您可以直接跳到答案的第二部分)。
您所谓的“复杂验证”实际上是个例外:业务密钥唯一性错误,低层或DB错误等。
提醒:Spring MVC中的验证是什么?
验证应在表示层上进行。它基本上是关于验证提交的表单字段。
我们可以将它们分为两种:
1)光验证 (有JSR-303 /hibernate验证):检查所提交的字段具有给定@Size
/
@Length
,它是@NotNull
或@NotEmpty
/ @NotBlank
,检查它具有@Email
格式等
2)重验证或复杂验证 主要涉及特定领域的验证案例,例如跨领域验证:
- 实施例1:该形式具有
fieldA
,fieldB
和fieldC
。每个字段可以单独为空,但其中至少一个不能为空。 - 示例2:如果
userAge
field的值小于18,则responsibleUser
field不能为null,并且responsibleUser
age必须大于21。
现在,我了解到,有了所有这些验证工具,再加上Spring根本不会打扰您,并且让您做任何您想做的事(不管是好是坏,这都是事实),就可以尝试使用“验证锤”来处理任何模糊不清的相关内容错误处理。
它将起作用:仅通过验证,您可以检查验证器/注释中的每个可能问题(并且几乎不会在较低层中引发任何异常)。这很糟糕,因为您祈祷自己考虑所有情况。您不会利用Java异常来简化逻辑并通过忘记检查某些内容是否有错误来减少犯错的机会。
因此,在Spring MVC世界中,不应将验证(即 UI验证 )误认为是下层 异常 ,例如服务异常或数据库异常(键唯一性等)。
如何以方便的方式处理Spring MVC中的异常?
有人认为“天哪,所以在我的控制器中,我将必须逐一检查所有可能的已检查异常,并考虑每个错误的消息错误?不能!”。我是那些人的其中一个。:-)
对于大多数情况,只需使用一些通用的检查异常类,您的所有异常都将得到扩展。然后只需使用@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";
}
}
简单,快捷,简便和清洁!
对于那些需要显示某些特定异常的错误消息,或者由于设计不良的旧系统而无法修改通用顶级异常的情况,只需添加其他@ExceptionHandler
。
另一个技巧:对于较混乱的代码,您可以使用来处理多个异常
@ExceptionHandler({MyException1.class, MyException2.class, ...})
public String yourMethod(Exception e, Model model) {
...
}
底线:何时使用验证?何时使用异常?
- UI中的错误=验证=验证工具(JSR-303批注,自定义批注,Spring验证程序)
- 来自较低层的错误=异常
当我说“来自UI的错误”时,我的意思是“用户以他的形式输入了错误的内容”。