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:表单有fieldA
、fieldB
和 fieldC
。每个字段可以单独为空,但至少其中一个不能为空。
示例 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";
简单、快速、简单、干净!
当您需要为某些特定异常显示错误消息时,或者由于无法修改的旧系统设计不佳而无法提供通用***异常时,只需添加其他 @ExceptionHandler
s。
另一个技巧:对于不那么混乱的代码,您可以使用
@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”之类的系统错误,但与@NotNull
check still大致相同,它需要复杂像 DB 一样处理。你会把这张支票放在哪里?如果在@ExceptionHandler
中,那么我无法理解为什么将其与BindingResult
支票分开。开发人员希望找到组合(或至少链接)在一起的所有验证步骤。如果控制器很大,将它们放入单独的方法中需要额外的脑力劳动(例如,“验证消息来自哪里?!不是来自 BindingResult!”)。以上是关于Spring MVC + Hibernate:数据验证策略的主要内容,如果未能解决你的问题,请参考以下文章
Spring+Spring MVC+Hibernate框架搭建实例
spring mvc+spring + hibernate 整合
如何使用 Hibernate、Spring MVC 将数据从单个表单发送到多个数据库表