【问题标题】:Hibernate Validator validation timingHibernate Validator 验证时间
【发布时间】:2014-02-08 02:57:17
【问题描述】:

我在谷歌上搜索了一下,时间不多了,所以我把它扔在这里,希望有人知道答案。我有一个系统,我在服务层有弹簧事务边界。下面是道层。我已经对我的模型对象进行了 bean 验证,并且我已经将 DAO 包装在编译时方面j 围绕方面,如下所示:

@Aspect
public class ValidationCollectorAspect {

  @Around("daoMethods()")
  public Object collectDaoMessages(ProceedingJoinPoint thisJoinPoint) throws Throwable {
    try {
      return thisJoinPoint.proceed();
    } catch (ConstraintViolationException e) {
      Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
      List<UserMessage> userMessages = ThreadContext.MESSAGES.get();
      for (ConstraintViolation<?> constraintViolation : constraintViolations) {
        userMessages.add(new UserMessage(constraintViolation.getMessage(), MessageType.VALIDATION));
      }
      throw new MyPersistenceException("Validation failures", e);
    }
  }

  @Pointcut("call(public * *(..)) &&!call(* getEntityManager()) && within(com.myclient.dao.impl..*)")
  public void daoMethods() {
  }
}

问题是验证似乎发生在事务提交时,而不是在 DAO 中的保存或更新操作之前。这意味着来自 bean 验证的 ConstraintViolationException 直到在服务方法返回之后并且在此连接点之后才被抛出。我的证据是 stack trace 不包含任何 dao 服务方法。我编写的代码的第一种方法由

显示
    at com.myclient.servlet.rest.Rest.updateObjects(Rest.java:323)

但这是一个 servlet 名称 Rest 上的方法,重点是不需要为系统中各种 servlet 上的一大堆特定方法创建连接点,并且还能够处理约束冲突在它被包裹在任意层的弹簧异常之前。

我知道有时在提交之前验证所有休眠更改的总和可能很酷,但这不是我想要的。 (虽然作为第二轮验证不会不受欢迎)当我在 dao 中调用 hibernate save 或 update 方法时,而不是在事务提交时,如何告诉 hibernate 验证器处理验证?

这是我构建的东西的版本:

compile 'org.hibernate:hibernate-entitymanager:4.2.2.Final'
compile 'org.hibernate:hibernate-validator:5.0.1.Final'
compile 'org.hibernate:hibernate-c3p0:4.2.2.Final'
compile 'org.springframework:spring-orm:3.2.3.RELEASE'
compile 'org.springframework:spring-context:3.2.3.RELEASE'
compile 'org.springframework:spring-web:3.2.3.RELEASE'
compile 'javax.inject:javax.inject:1'
compile 'org.aspectj:aspectjrt:1.7.3'
ajc "org.aspectj:aspectjtools:1.7.3"

编辑:进一步说明...我在 JPA 下完成所有这些工作,因此如果存在非休眠特定解决方案,我更喜欢它。

【问题讨论】:

    标签: java spring hibernate aspectj bean-validation


    【解决方案1】:

    当我在 dao 中调用 hibernate save 或 update 方法时,而不是在事务提交时,如何告诉 hibernate 验证器处理验证?

    实现hibernate entity listeners 并在您想要进行验证时为每个event type 注册事件侦听器。

    看看this question如何使用hibernate 4配置事件监听器。

    【讨论】:

    • 我无法将其与此处所说的内容进行协调... docs.jboss.org/hibernate/validator/5.1/reference/en-US/… 据此,它应该已经被调用以进行预插入。如果这还没有到位,它是如何被调用的?
    • docs.jboss.org/hibernate/validator/5.1/reference/en-US/… too...我在上面添加了建议的道具(我忘了说我使用的是 JPA)。仍然没有快乐。
    • 然后使用JPA实体监听器。
    • @MarkusMalkusch 是的,我试过了,这也被推迟到提交,这意味着它也被包裹在 RollbackException 中,我必须在原因链中找到它。正如您在我的解决方案中看到的那样,我最终放弃了,只是通过异常原因链寻找它是否包含ConstraintViolationException。似乎hibernate只听需要做的事情,直到提交时间才做出反应。这对批处理很有意义,但让这个任务变得不直观。
    • @Gus 你试过@Pre* 回调吗?
    【解决方案2】:

    (Stack Overflow 说这太长了,不能作为评论发布,所以把它作为答案发布。)

    我想了想你想要做什么,但不知道动机是什么。所以现在我很好奇。

    如果您从其他代码中接收对象并希望它是@Valid,您应该在对其进行任何操作之前对其进行验证;不是在调用save()update() 时。您可以直接将其传递给验证器,也可以使用 Hibernate Validator 5 中的新方法约束支持,这将为您提供使用代理的方法验证。

    如果您自己构建对象并试图在持续存在时捕获代码错误,那就是代码异味。为什么你没有构造函数或构造函数来阻止无效实体的构造?如果你确实有它们,但只是想在你坚持时仔细检查,如果失败发生在save() 时间或事务提交时会有什么不同,因为整个事情无论如何都应该回滚?

    您还应该记住,Hibernate 执行事务性后写操作,并延迟保存,希望它们可以合并在一起以减少与数据库的冲突。因此,如果您希望验证作为保存的副作用,您还应该期望验证会被推迟,否则您会弄乱语义。

    无论如何,我会在 DAO 级别尝试 HV5 method constraints,并跳过自定义验证器作为第一种方法。

    【讨论】:

    • 这是一个 REST 接口,我使用 jackson 从 json 自动创建对象,然后经过几次检查保存它(如果它通过了这些验证)。有时收到的 JSON 是一个对象图,遍历该图以查找和验证对象是非常混乱的。 Hibernate 必须遍历图表以将其持久化,因此我只是在已经完成的工作之上搭载。什么时候很重要,因为我希望在我的连接点内出现异常,而不是在外面。如果任何验证失败,交易不会继续进行。
    • 为什么验证对象图会“很麻烦”?如果你使用 Spring MVC 来实现,我会在控制器上添加一个 @Valid ,你就完成了。 (docs.spring.io/spring/docs/4.0.0.RELEASE/…) 如果不是,则在服务级别而不是 DAO 级别上验证或注释是单行的。否则你会让代码在潜在的无效对象上工作,并且只有在持久化时才会爆炸。
    • 代码所做的工作是持久化对象。这是代表状态转移中的 REST。该应用程序的全部工作是验证发送的数据并将其输入数据库。 “对无效数据进行操作”无关紧要。如果某些事情无效,那么整个事情都会失败。它不是spring mvc。没有用户界面(至少在这场战争中)。 Spring 在这里只做依赖注入和事务边界。
    • 换句话说,这项服务说明了以下三件事之一:1. 我不知道你会离开,2. 我不会接受它...不,我不会接受它.. 和 3. 谢谢先生!我可以再来一个吗! (对于 PUT 和 POST 操作,GET 当然略有不同但与这个问题无关)
    • 我猜是各有各的。我会首先进行验证,而不是将其与持久性结合起来,如果性能有问题,则为 Hibernate 禁用它。
    【解决方案3】:

    我不是 100% 确定我喜欢这个,但我是这样解决的。基本上我不再尝试寻找休眠解决方案并添加了一些额外的方法......

    在我的服务层:

      @Override
      public void save(T r) {
        // This allows us to post a join point just outside the transaction boundary
        save0(r);
      }
    
      @Transactional
      private void save0(T r) {
        getDao().save(r);
      }
    
      @Override
      public T update(T obj) throws IllegalArgumentException {
        // This allows us to post a join point just outside the transaction boundary
        return update0(obj);
      }
    
      @Transactional
      private T update0(T obj) {
        return getDao().update(obj);
      }
    

    在我的aspectJ切入点

      @Around("daoMethods()")
      public Object collectDaoMessages(ProceedingJoinPoint thisJoinPoint) throws Throwable {
        try {
          return thisJoinPoint.proceed();
        } catch (Exception e) {
          ConstraintViolationException cve;
          Throwable cause = e;
          while (cause != null && !(cause instanceof ConstraintViolationException)) {
            cause = cause.getCause();
          }
          if (cause == null) {
            throw e;
          } else {
            cve = (ConstraintViolationException) cause;
          }
    
          Set<ConstraintViolation<?>> constraintViolations = cve.getConstraintViolations();
          List<UserMessage> userMessages = ThreadContext.MESSAGES.get();
          for (ConstraintViolation<?> constraintViolation : constraintViolations) {
            userMessages.add(new UserMessage(constraintViolation.getMessage(), MessageType.VALIDATION));
          }
          throw new MyPersistenceException("Validation failures", e);
        }
      }
    
      @Pointcut(
          "execution(public * *(..)) " +
          "&& !execution(public * load*(..)) " +
          "&& within(com.myclient.service.impl..*) "
      )
      public void daoMethods() {}
    

    【讨论】:

    • 由于您已将方面置于服务级别,您可以尝试增加自己方面的优先级(设置@Order),而不是手动委派。这将确保事务在您的异常检查运行之前尝试提交。它还避免了强制为@Transactional 使用 aspectj 代理模式,因为您现在正在注释私有方法。
    • 那行不通,因为 aspectj 方面是在编译时作为字节码添加的。不过,我不知道“代理模式”,你能给我指个参考吗?费用是多少? (我不必进行任何不同的配置,它就可以工作)
    • 您可以在docs.spring.io/spring/docs/4.0.0.RELEASE/… 找到信息。寻找“公众知名度”并点击链接。回复:编织,如果您避免编译时编织并使用 Spring AOP,您将处于更简单的水域,但这取决于您的要求。如果这些私有事务方法有效,我猜你有 CGLIB 代理来支持 Spring 的事务支持和 ajc 为你的方面做编译时编织。通过不编织,我认为这些方面将在同一个竞争环境中,让您订购它们。
    • 作为后续,私有方法不起作用(如文档所述),直到我使用 AspectJ 编译器(构建时间)应用了 Spring 方面。如果我有机会支持并使用@Order 并且它成功了,我可能会更新这个答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多