You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:09:15