0

I've been googling a bit and I'm running out of time so I'm throwing this out here in the hopes that someone just knows the answer. I've got a system where I have spring transaction boundaries at the Service layer. Below that lies a dao layer. I've got bean validation on my model objects and I've wrapped the DAO's in a compile time aspectj Around aspect like this:

@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() {
  }
}

The problem is the validation appears to happen when the transaction is committed, not prior to the save or update operation in the DAO. This means that the ConstraintViolationException from bean validation is not thrown until AFTER the service method returns and way after this join point. My evidence for this is that the stack trace does not contain any of the dao service methods. The first method for code I wrote is shown by

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

But that's a method on a servlet name Rest, and the whole point is to NOT need to create join points for a whole bunch of specific methods on the various servlets in the system, and also to be able to process the Constraint violation before it's wrapped in arbitrary layers of spring exceptions.

I understand that sometimes it might be cool to validate the sum total of all the hibernate changes, just before commit, but that's not what I want. (although as second round of validation it would not be unwelcome) How can I tell hibernate validator to process the validation when I call the hibernate save or update methods in the dao, not when the transaction commits?

Here's the versions of stuff from my build:

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"

Edit: One further note... I'm doing all this under JPA, so I'd prefer a non-hibernate specific solution if one exists.

Gus
  • 6,719
  • 6
  • 37
  • 58

3 Answers3

0

How can I tell hibernate validator to process the validation when I call the hibernate save or update methods in the dao, not when the transaction commits?

Implement a hibernate entity listeners and register the event listener for every event type when you want to do the validation.

Take a look at this question on how to configure event listeners with hibernate 4.

Community
  • 1
  • 1
René Link
  • 48,224
  • 13
  • 108
  • 140
  • I'm having trouble reconciling that with what it says here... https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html_single/#validator-checkconstraints-orm-hibernateevent According to that, it should already be called for pre-insert. If that's not already in place how is it getting called at all? – Gus Jan 18 '14 at 20:35
  • https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html_single/#d0e5170 too... I added the suggested props (I forgot to say I was using JPA) above. Still no joy. – Gus Jan 18 '14 at 20:42
  • 1
    Then use JPA entity listener. – Markus Malkusch Jan 19 '14 at 07:12
  • @MarkusMalkusch Yeah, I tried that and this too gets deferred until commit, meaning it also gets wrapped in the RollbackException, and I have to find it in the cause chain. As you can see in my solution I finally gave up and just went hunting through the exception cause chain to see if it contained a `ConstraintViolationException` anyway. It seems hibernate just listens to what needs to be done without reacting until commit time. This makes sense for batching, but made this task counter intuitive. – Gus Jan 19 '14 at 15:40
  • @Gus You did try with the @Pre* callbacks? – Markus Malkusch Jan 20 '14 at 04:37
  • yup,@PrePersist. @PreUpdate. - and still seemed to happen at commit time – Gus Jan 21 '14 at 18:42
  • @Gus I guess your hibernate flush mode is "COMMIT". You can than either set the flush mode to "ALWAYS" (but this flushes the session before every query) or you can flush manually `Session.flush()`. Also see http://docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/FlushMode.html – René Link Jan 21 '14 at 19:30
0

(Stack Overflow says this is too long to post as a comment, so posting it as an answer instead.)

I thought a bit about what you're trying to do, and can't figure out what the motivation is. So now I'm curious.

If you're receiving the object from some other piece of code and want it to be @Valid, you should be validating it before doing anything with it; not when calling save() or update(). You can either directly pass it to a validator or use the new method constraint support in Hibernate Validator 5 which will give you method validation using proxies.

If you're constructing the object yourself and are trying to catch code errors when you persist, that's a code smell. Why don't you have constructors or builders that prevent the construction of an invalid entity in the first place? And if you do have them but just want the double check when you persist, what difference would it make if the failures happen at save() time or at transaction commit, since the whole thing should roll back anyway?

You should also keep in mind that Hibernate does transactional write-behind and saves are deferred in the hope that they can be merged together to reduce chatter with the database. So if you want validation as a side-effect of saving, you should also expect that validation to be deferred or you're messing with semantics.

Anyway, I'd try HV5 method constraints at the DAO level and skip the custom validator as a first approach.

Emerson Farrugia
  • 11,153
  • 5
  • 43
  • 51
  • This is a REST interface, I'm automatically creating the object from json using jackson, and then after a few checks saving it (if it passes these validations). Sometimes the recieved JSON is a graph of objects and traversing the graph to find and validate the objects is way messy. Hibernate has to traverse the graph to persist it so this way I just piggyback on top of that work already done. When matters because I want the exception inside my joinpoint, not outside. There are no cases where the transaction should proceed if any validation fails. – Gus Jan 19 '14 at 00:11
  • Why would it be "way messy" to validate the object graph? If you're using Spring MVC for your implementation, I'd throw in an @Valid on the controller and you're done. (http://docs.spring.io/spring/docs/4.0.0.RELEASE/spring-framework-reference/htmlsingle/#validation-mvc) If not, it's a one liner to validate, or annotate on the service level instead of the DAO level. Otherwise you're letting code work on potentially invalid objects, and only blowing up when persisting. – Emerson Farrugia Jan 19 '14 at 00:21
  • The work the code is doing is persisting the object. This is REST as in representational state transfer. The entire job of this application is to validate the data sent and get it into the database. "operating on invalid data" doesn't matter. If somethings invalid the whole thing is going to fail. It is not spring mvc. There is no UI (in this war at least). Spring is only doing the dependency injection and transaction boundaries here. – Gus Jan 19 '14 at 01:22
  • To put it another way this service says one of 3 things: 1. I don't know you go away, 2. I'm not gonna take it... no I'm not gonna take it.. and 3. Thankyou sir! may I have another! (for the PUT and POST operations, GET of course is slightly different but irrelevant to this question) – Gus Jan 19 '14 at 01:24
  • To each his own, I guess. I would have made validation the first thing that happens instead of coupling it with persistence, disabling it for Hibernate if performance is an issue. – Emerson Farrugia Jan 19 '14 at 15:09
0

I'm not 100% sure I like this, but here's how I solved it. Basically I stopped trying to find a hibernate solution and added a couple of extra methods...

In my service layer:

  @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);
  }

In my aspectJ pointcut

  @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() {}
Gus
  • 6,719
  • 6
  • 37
  • 58
  • Since you've put the aspects at the service level, you could just try increasing the precedence (set an `@Order`) of your own aspect, instead of manually delegating. That would make sure the transaction attempts to commit before your exception checks run. It also avoids being forced to use aspectj proxy mode for @Transactional, since you're now annotating private methods. – Emerson Farrugia Jan 19 '14 at 15:12
  • That won't work because the aspectj aspect is added as bytecode at compiletime. I'm unaware of "proxy mode" though, can you point me to a reference. What is the cost? (I didn't have to configure anything differently, it just worked) – Gus Jan 19 '14 at 15:23
  • You can find info at http://docs.spring.io/spring/docs/4.0.0.RELEASE/spring-framework-reference/htmlsingle/#transaction-declarative-annotations. Look for "public visibility" and follow the links. re: weaving, you'd be in simpler waters if you avoid compile-time weaving and use Spring AOP, but it depends on your requirements. If those private transactional methods are working, my guess is you've got CGLIB proxies for Spring's transactional support and ajc doing compile time weaving for your aspect. By not weaving, I think the aspects would be on the same playing field, letting you order them. – Emerson Farrugia Jan 19 '14 at 16:21
  • By way of followup, the private methods did not work (as documented) until I applied the Spring aspects using the AspectJ compiler (build time). I may update this answer if I get a chance to back and play with @Order and it works out. – Gus Feb 18 '14 at 23:30