如何创建带范围验证的自定义校验注解?库存验证异常排查
库存范围校验问题排查与修复
问题背景
需要实现库存值范围校验:当库存低于最小值或高于最大值时触发错误提示。尝试使用@Range、@Min等标准校验注解无法满足需求,自定义了@invrange注解及对应校验器,但仍出现异常,怀疑是注解属性绑定变量的问题。
现有代码
自定义注解代码
package com.example.demo.validators; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Constraint(validatedBy = {invrangeValidator.class}) @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface invrange { String message() default "库存值必须在最小值和最大值之间!"; Class<?> [] groups() default{}; Class<? extends Payload> [] payload() default {}; int maxInv(); int minInv(); }
校验器代码
package com.example.demo.validators; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class invrangeValidator implements ConstraintValidator<invrange, Integer> { private int maxInv; private int minInv; @Override public void initialize(invrange constraintAnnotation) { maxInv = constraintAnnotation.maxInv(); minInv = constraintAnnotation.minInv(); } @Override public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) { if (integer > maxInv && integer < minInv) { return false; } else { return true; } } }
实体类代码
package com.example.demo.domain; import com.example.demo.validators.ValidDeletePart; import com.example.demo.validators.invrange; import org.hibernate.validator.constraints.Range; import javax.persistence.*; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.Size; import java.io.Serializable; import java.util.HashSet; import java.util.Set; @Entity @ValidDeletePart @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="part_type",discriminatorType = DiscriminatorType.INTEGER) @Table(name="Parts") public abstract class Part implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) long id; String name; @Min(value = 0, message = "价格必须为正数") double price; int inv; int maxInv; int minInv; @ManyToMany @JoinTable(name="product_part", joinColumns = @JoinColumn(name="part_id"), inverseJoinColumns=@JoinColumn(name="product_id")) Set<Product> products= new HashSet<>(); public Part() {} public Part(String name, double price, int inv, int maxInv, int minInv) { this.name = name; this.price = price; this.inv = inv; this.maxInv = maxInv; this.minInv = minInv; } public Part(long id, String name, double price, int inv, int maxInv, int minInv) { this.id = id; this.name = name; this.price = price; this.inv = inv; this.maxInv = maxInv; this.minInv = minInv; } // 省略getter/setter方法 public int getInv() { return inv; } @invrange(maxInv = minInv, minInv = maxInv) public void setInv(int inv) { this.inv = inv; } public int getMaxInv() {return maxInv;} public void setMaxInv(int maxInv){this.maxInv = maxInv;} public int getMinInv(){return minInv;} public void setMinInv(int minInv){this.minInv = minInv;} // 省略equals、hashCode、toString方法 }
表单代码
<!DOCTYPE html> <html lang="en"> <html xmlns:th="https://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>零件表单</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>外协零件详情</h1> <form action="#" th:action="@{/showFormAddOutPart}" th:object="${outsourcedpart}" method="POST"> <p><input type="hidden" th:field="*{id}"/></p> <p>名称:</p> <p><input type="text" th:field="*{name}" placeholder="名称" class="form-control mb-4 col-4"/></p> <p>价格:</p> <p><input type="text" th:field="*{price}" placeholder= "价格" class="form-control mb-4 col-4"/></p> <p th:if="${#fields.hasErrors('price')}" th:errors="*{price}">价格错误</p> <p>库存:</p> <p><input type="text" th:field="*{inv}" placeholder="库存" class="form-control mb-4 col-4"/></p> <p th:if="${#fields.hasErrors('inv')}" th:errors="*{inv}">库存错误</p> <p>最大库存:</p> <p><input type="text" th:field="*{maxInv}" placeholder="最大库存" class="form-control mb-4 col-4"></p> <p>最小库存:</p> <p><input type="text" th:field="*{minInv}" placeholder="最小库存" class="form-control mb-4 col-4"></p> <p>公司名称:</p> <p><input type="text" th:field="*{companyName}" placeholder="公司名称" class="form-control mb-4 col-4"/></p> <p><input type="submit" value="提交" /></p> </form> <a href="http://localhost:8080/">返回主页面</a> </body> </html>
问题分析
- 注解属性绑定错误:实体类
setInv方法上的@invrange(maxInv = minInv, minInv = maxInv)写法错误,注解属性无法直接引用实体类的字段值,JSR-380标准不支持字段间的直接引用。 - 校验逻辑错误:校验器中的
if (integer > maxInv && integer < minInv)逻辑矛盾,应该用||(或)判断库存超出范围的情况。 - 校验器类型不匹配:当前校验器仅接收单个
Integer值,无法获取实体类中的maxInv和minInv字段值,需要改为校验整个实体对象。
修复方案
1. 修改自定义注解为类级别校验
package com.example.demo.validators; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Constraint(validatedBy = {InvrangeValidator.class}) @Target({ElementType.TYPE}) // 修改为类级别校验 @Retention(RetentionPolicy.RUNTIME) public @interface Invrange { String message() default "库存值必须在最小值和最大值之间!"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
2. 修改校验器,校验整个Part对象
package com.example.demo.validators; import com.example.demo.domain.Part; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class InvrangeValidator implements ConstraintValidator<Invrange, Part> { @Override public void initialize(Invrange constraintAnnotation) {} @Override public boolean isValid(Part part, ConstraintValidatorContext context) { if (part == null) return true; int inv = part.getInv(); int maxInv = part.getMaxInv(); int minInv = part.getMinInv(); // 修复逻辑:库存超出范围时返回false if (inv > maxInv || inv < minInv) { // 将错误提示绑定到inv字段 context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()) .addPropertyNode("inv") .addConstraintViolation(); return false; } return true; } }
3. 在实体类上添加类级别注解
@Entity @ValidDeletePart @Invrange // 添加类级别校验注解 @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="part_type",discriminatorType = DiscriminatorType.INTEGER) @Table(name="Parts") public abstract class Part implements Serializable { // ... 移除setInv方法上的@invrange注解,其他代码不变 }
4. 控制器触发校验
确保控制器方法添加@Valid注解触发校验:
@PostMapping("/showFormAddOutPart") public String addOutsourcedPart(@Valid @ModelAttribute("outsourcedpart") OutsourcedPart part, BindingResult result) { if (result.hasErrors()) { return "outsourced-part-form"; // 返回表单展示错误 } // 执行保存逻辑 return "redirect:/"; }
内容的提问来源于stack exchange,提问作者user22196580
相关产品推荐
相关产品推荐

