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:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 an 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 down’t really show the obvious miss take — only if you compare it with the save method the problem gets pretty clear:
ValidatingMongoEventListener
1 2 3 4 5 6 7 8 9 10 11 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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.
1 2 3 4 5 6 7 8 9 |
// JSR Validation for MongoDB Entities @Bean public ValidatingMongoEventListener validatingMongoEventListener() { return new ValidatingMongoEventListener(validator()); } @Bean public LocalValidatorFactoryBean validator() { return new LocalValidatorFactoryBean(); } |
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 😉
Seems that posts in this website do not allow XML code… :-(. In any case, should not FixedMongoValidation be somehow specified in the configuration class? Thanks!
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 version1.9.1.RELEASE
.