Spring Boot中基于Hibernate Validator自定义@Updatable校验注解问题
嘿,看你已经在着手实现自定义的字段更新校验注解了,我来帮你把这个功能补全捋顺,让它在Spring Boot + Hibernate Validator环境下完美跑起来~
实现自定义@Updatable字段更新校验注解
1. 先定义@Updatable注解本身
首先得把注解的骨架搭好,包含咱们需要的校验模式(比如你提到的WHILE_NEW),还要指定对应的校验器:
import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = UpdatableConstraintValidator.class) @Documented public @interface Updatable { // 自定义校验模式枚举,满足不同场景需求 enum Mode { WHILE_NEW, // 仅实体*新建状态*(未持久化)时允许修改 ALWAYS, // 任何场景都能改 NEVER // 啥时候都不能改 } // 默认用WHILE_NEW模式,和你需求匹配 Mode value() default Mode.WHILE_NEW; // 校验失败的提示消息,可自定义 String message() default "该字段不允许修改"; // 校验分组,用于多场景校验 Class<?>[] groups() default {}; // 负载信息,用于传递额外校验元数据 Class<? extends Payload>[] payload() default {}; }
2. 完善校验器UpdatableConstraintValidator
你已经写了初始化方法,接下来咱们把核心的校验逻辑补上。这里的关键是判断实体是新建状态(没存到数据库,ID为空)还是更新状态(已持久化,ID存在),然后根据注解指定的模式做校验:
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; public class UpdatableConstraintValidator implements ConstraintValidator<Updatable, Object> { private Updatable.Mode updatableMode; // 默认用"id"作为判断实体状态的字段,要是你项目里用别的字段(比如"uuid"),可以在注解里加配置项 private String idField = "id"; @Override public void initialize(Updatable updatable) { this.updatableMode = updatable.value(); // 要是你想支持自定义ID字段,这里可以加一行读取注解配置的代码: // this.idField = updatable.idField(); } @Override public boolean isValid(Object entity, ConstraintValidatorContext context) { // 用BeanWrapper来操作实体属性,避免反射的繁琐 BeanWrapper beanWrapper = new BeanWrapperImpl(entity); Object idValue = beanWrapper.getPropertyValue(idField); // 判断是否是新建实体:ID为空,或者数值型ID等于0 boolean isNew = idValue == null || (idValue instanceof Number && ((Number) idValue).longValue() == 0); switch (updatableMode) { case WHILE_NEW: // 核心逻辑:新建时随便改,更新时不能改这个字段 // 简化版(适合创建时间这类字段,更新时不允许传新值): return isNew || beanWrapper.getPropertyValue(context.getPropertyName()) == null; // 更贴合实际业务的逻辑(需要获取数据库原实体,判断字段是否被篡改): // Object originalValue = getOriginalEntity(entity).getPropertyValue(context.getPropertyName()); // return isNew || originalValue.equals(beanWrapper.getPropertyValue(context.getPropertyName())); case ALWAYS: // 啥情况都通过 return true; case NEVER: // 啥情况都不通过 return false; default: return true; } } // 可选:获取数据库中原实体的方法,需要注入对应Repository // private <T> T getOriginalEntity(T entity) { // // 示例:通过ID查询原数据,这里可以用泛型Repository简化 // // return userRepository.findById((Long) beanWrapper.getPropertyValue(idField)).orElse(null); // } }
3. 在实体类里用起来
直接在需要校验的字段上标注@Updatable就行,指定对应的模式:
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 仅新建用户时能设置用户名,更新时不许改 @Updatable(Updatable.Mode.WHILE_NEW) private String username; // 邮箱随时能改 @Updatable(Updatable.Mode.ALWAYS) private String email; // 用户类型一旦设置就不能改 @Updatable(Updatable.Mode.NEVER) private String userType; // getter、setter省略 }
4. 触发校验
在Spring Boot的Controller里,只要给请求参数加上@Valid或者@Validated,就能自动触发校验:
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; @RestController public class UserController { @PostMapping("/users") public String createUser(@Valid @RequestBody User user) { // 保存新建用户,用户名可以正常设置 return "用户创建成功"; } @PutMapping("/users/{id}") public String updateUser(@Valid @RequestBody User user) { // 更新用户时,如果改了username,校验会直接失败,返回错误信息 return "用户更新成功"; } }
一些实用小提示
- 如果需要在更新时精准判断字段是否被修改,得在校验器里获取数据库的原实体数据,可以通过注入Repository,或者用Spring AOP在校验前把原数据放到ThreadLocal里,在校验器中读取。
- 可以根据业务需求扩展Mode枚举,比如加个
WHILE_INACTIVE模式,针对状态为未激活的实体允许修改。 - 要是需要国际化的错误提示,可以把message值指向
ValidationMessages.properties里的键,比如message = {updatable.field.not.allowed}。
内容的提问来源于stack exchange,提问作者Matúš Zbojník
相关产品推荐
相关产品推荐

