如何在Spring Data R2DBC中实现实体校验?
Great question! It’s true that Spring Data R2DBC doesn’t support Bean Validation annotations (from javax.validation.constraints.* or Hibernate Validator’s org.hibernate.validator.constraints.*) out of the box like Spring Data JPA does. But you’re right—you don’t need full ORM capabilities to add pre-persistence field checks. Here are three simple, fluent approaches to implement entity validation for Spring Data R2DBC:
1. Use R2DBC’s EntityCallbacks for Global Pre-Save Validation
R2DBC has a built-in callback mechanism that’s perfect for this use case. You can create a global callback that runs validation on any entity before it’s saved.
Step 1: Add Bean Validation Dependencies
First, make sure your project includes the validation libraries. If you’re using Spring Boot, add this starter:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
Or for non-Spring Boot projects, add the API and Hibernate Validator implementation:
<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency>
Step 2: Create a Custom BeforeSaveCallback
Implement the BeforeSaveCallback interface to hook into the pre-save lifecycle and trigger validation:
import org.springframework.data.r2dbc.mapping.event.BeforeSaveCallback; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.stereotype.Component; import javax.validation.ConstraintViolationException; import javax.validation.Validator; import java.util.Set; @Component public class GlobalEntityValidationCallback implements BeforeSaveCallback<Object> { private final Validator validator; public GlobalEntityValidationCallback(Validator validator) { this.validator = validator; } @Override public Object onBeforeSave(Object entity, SqlIdentifier table) { Set<?> violations = validator.validate(entity); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } return entity; } }
This callback will automatically run for every entity before it’s saved. If any validation constraints are violated, it throws a ConstraintViolationException to block persistence.
2. Add Validation to Specific Repositories
If you don’t want global validation, you can add checks directly to custom Repository implementations.
Step 1: Define Your Repository Interface
import org.springframework.data.r2dbc.repository.ReactiveCrudRepository; public interface UserRepository extends ReactiveCrudRepository<User, Long> { }
Step 2: Implement the Repository with Validation
Extend SimpleR2dbcRepository and override the save method to add validation logic:
import org.springframework.data.r2dbc.repository.support.SimpleR2dbcRepository; import org.springframework.data.r2dbc.core.R2dbcEntityOperations; import javax.validation.ConstraintViolationException; import javax.validation.Validator; import java.util.Set; public class UserRepositoryImpl extends SimpleR2dbcRepository<User, Long> implements UserRepository { private final Validator validator; public UserRepositoryImpl(R2dbcEntityOperations entityOperations, Validator validator) { super(entityOperations); this.validator = validator; } @Override public <S extends User> reactor.core.publisher.Mono<S> save(S entity) { Set<?> violations = validator.validate(entity); if (!violations.isEmpty()) { return reactor.core.publisher.Mono.error(new ConstraintViolationException(violations)); } return super.save(entity); } }
This approach targets only the UserRepository, making it ideal for cases where you need validation on specific entities.
3. Reactive-Style Validation in Service Layer
For a more idiomatic Reactive approach, integrate validation directly into your service’s data flow using Spring’s ReactiveValidator.
Example Service Implementation
import org.springframework.stereotype.Service; import org.springframework.validation.ReactiveValidator; import org.springframework.validation.beanvalidation.SpringValidatorAdapter; import javax.validation.ConstraintViolationException; import javax.validation.Validator; import reactor.core.publisher.Mono; @Service public class UserService { private final UserRepository userRepository; private final ReactiveValidator reactiveValidator; public UserService(UserRepository userRepository, Validator validator) { this.userRepository = userRepository; this.reactiveValidator = new SpringValidatorAdapter(validator); } public Mono<User> createUser(User user) { return reactiveValidator.validate(user) .flatMap(violations -> { if (!violations.isEmpty()) { return Mono.error(new ConstraintViolationException(violations)); } return userRepository.save(user); }); } }
This wraps validation into the reactive stream, ensuring checks run before the entity is passed to the repository.
Don’t Forget the Entity Annotations
Whichever approach you choose, annotate your entity fields just like you would in JPA:
import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; public class User { private Long id; @NotNull(message = "Username cannot be null") @Size(min = 2, max = 20, message = "Username must be between 2 and 20 characters") private String username; // Getters and setters }
内容的提问来源于stack exchange,提问作者fyrkov

