Spring Boot+MongoDB中实现抽象类字段持久化强制校验
appId is Valid for All Parent Subclasses on MongoDB Persistence Hey there! Let's solve this problem where you want to guarantee that every subclass of Parent has a valid non-null appId when saved to MongoDB—without writing repetitive checks in every service or repository. Here's how to implement this with Spring Data MongoDB's lifecycle hooks and Bean Validation:
Step 1: Add Bean Validation Dependency
First, you need to include the Spring Validation starter in your build.gradle to use @NotNull and the Validator API:
dependencies { // ... your existing dependencies implementation 'org.springframework.boot:spring-boot-starter-validation' }
Step 2: Option 1 - Use @PrePersist in the Abstract Parent Class
You can add a validation method directly to your Parent class using the @PrePersist annotation. This method will automatically run right before any subclass is persisted:
import javax.validation.constraints.NotNull; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import org.springframework.data.mongodb.core.mapping.event.PrePersist; import java.util.Set; public abstract class Parent { @NotNull(message = "appId must not be null or empty") private String appId; @PrePersist protected void validateAppId() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); Set<ConstraintViolation<Parent>> violations = validator.validate(this); if (!violations.isEmpty()) { throw new ConstraintViolationException("Validation failed for Parent entity", violations); } } // Getters and Setters for appId }
Since Parent is abstract, all its subclasses (child1, child2) will inherit this validation logic automatically.
Step 3: Option 2 - Global MongoDB Event Listener (Recommended)
If you prefer not to modify the Parent class, create a global event listener that intercepts all MongoDB save operations and validates any instance of Parent:
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent; import org.springframework.stereotype.Component; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import java.util.Set; @Component public class ParentEntityValidationListener extends AbstractMongoEventListener<Object> { private final Validator validator; public ParentEntityValidationListener() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); this.validator = factory.getValidator(); } @Override public void onBeforeSave(BeforeSaveEvent<Object> event) { Object entity = event.getSource(); // Check if the entity is a subclass of Parent if (entity instanceof Parent) { Set<ConstraintViolation<Parent>> violations = validator.validate((Parent) entity); if (!violations.isEmpty()) { throw new ConstraintViolationException( "Cannot persist entity: appId is required", violations ); } } super.onBeforeSave(event); } }
This listener is a Spring-managed component, so it will automatically register itself with Spring Data MongoDB. Any time a child1 or child2 instance is saved, it will trigger the validation for appId.
How It Works
- Both approaches use JSR-380 Bean Validation to check the
@NotNullconstraint onappId. - If
appIdis null or empty (depending on your constraint), aConstraintViolationExceptionwill be thrown before the entity is saved to MongoDB. - You can handle this exception globally using a
@RestControllerAdviceclass to return user-friendly error responses to your clients.
Testing the Solution
Try saving a child1 instance with appId set to null:
Child1 child = new Child1(); child.setId("123"); // child.setAppId(null); // Leave appId unset childRepository.save(child);
This should immediately throw a ConstraintViolationException, preventing the invalid entity from being persisted.
内容的提问来源于stack exchange,提问作者Syed Zishan

