Spring Data MongoDB and JSR Validation using Lazy DBRef

The Problem

Recently using MongoDB and Spring Data I encountered an interesting issue. Saving an Entity  which was loaded by @DBRef(lazy = true) wasn’t possible together with the activated ValidatingMongoEventListener. Each time the following error occurred:

javax.validation.ConstraintViolationException
	at org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener.onBeforeSave(ValidatingMongoEventListener.java:65)
	at org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener.onBeforeSave(AbstractMongoEventListener.java:144)
	at org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener.onApplicationEvent(AbstractMongoEventListener.java:90)
	at org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener.onApplicationEvent(AbstractMongoEventListener.java:33)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:163)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:136)
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:381)
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:335)
	at org.springframework.data.mongodb.core.MongoTemplate.maybeEmitEvent(MongoTemplate.java:1633)
	at org.springframework.data.mongodb.core.MongoTemplate.doSave(MongoTemplate.java:986)
	at org.springframework.data.mongodb.core.MongoTemplate.save(MongoTemplate.java:933)
	at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:78)

The Issue

Well, looking into the details it is quite clear — as a dynamic proxy is passed to the validation which fields are all null. As so any validation on the Entity will always fail. The code of the validator itself doesn’t really show the obvious miss take — only if you compare it with the save method the problem gets pretty clear.

ValidatingMongoEventListener

public void onBeforeSave(Object source, DBObject dbo) {

  LOG.debug("Validating object: {}", source);
  Set violations = validator.validate(source);

  if (!violations.isEmpty()) {

    LOG.info("During object: {} validation violations found: {}", source, violations);
    throw new ConstraintViolationException(violations);
  }
}

MappingMongoConverter

Compared to the Spring Mongo Mapper code, which is responsible to store the entity it’s clear what we have to do.

public void write(final Object obj, final DBObject dbo) {

  if (null == obj) {
    return;
  }

  Class<?> entityType = obj.getClass();
  boolean handledByCustomConverter = conversions.getCustomWriteTarget(entityType, DBObject.class) != null;
  TypeInformation<? extends Object> type = ClassTypeInformation.from(entityType);

  if (!handledByCustomConverter && !(dbo instanceof BasicDBList)) {
    typeMapper.writeType(type, dbo);
  }

  Object target = obj instanceof LazyLoadingProxy ? ((LazyLoadingProxy) obj).getTarget() : obj;

  writeInternal(target, dbo, type);
}

Solution

As so we have to replace the Spring provided Validation Listener with an own — till Spring fixes the issue:

public class FixedMongoValidation  extends AbstractMongoEventListener<Object> {
  private final Validator validator;
  public FixedMongoValidation(Validator validator) {
    Assert.notNull(validator);
    this.validator = validator;
  }
  @Override
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public void onBeforeSave(Object obj, DBObject dbo) {

    Object target = obj instanceof LazyLoadingProxy ? ((LazyLoadingProxy) obj).getTarget() : obj;
    Set violations = validator.validate(target);

    if (!violations.isEmpty()) {

      LOG.info("During object: {} validation violations found: {}", target, violations);
      throw new ConstraintViolationException(violations);
    }
  }
}

And of course don’t forget to change the configuration to ensure your fixed class is used.

// JSR Validation for MongoDB Entities
@Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
  return new ValidatingMongoEventListener(validator());
}
@Bean
public LocalValidatorFactoryBean validator() {
  return new LocalValidatorFactoryBean();
}

Links

Pull Request in Spring Data Project

Paul Sterl has written 51 articles

3 thoughts on “Spring Data MongoDB and JSR Validation using Lazy DBRef

  1. Hi there!

    I am having the same issue and followed your same steps. However, both my custom validation listener and Spring’s are being executed.

    I am using Spring 3.2.9 and XML configuration instead, and tried the following configurations:

    Any help fixing this issue would be more than appreciated 😉

      1. Paul Sterl says:

        Yes it needs to be defined in the configuration class / xml. The described behavior points that both validator classes are in the Spring Context active in your case now. (The fixed one and the broken from Spring).

        I would check where the current spring validator is coming from. Either from a imported namespace or some auto magic. Usually (in spring-data) the validator isn’t configured by default. I would assume the same for here now for the XML configuration. –> Means I think you have somewhere in XML the spring validator too. Does this make sense?

        The question is also not about the spring core version but Spring Data / Mongo version. Nevertheless my patch till now didn’t made it into the lib, I can say for sure that spring-data-mongodb is affected until version 1.9.1.RELEASE.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>