Hibernate Validator案例

wen java案例 2

本文目录导读:

Hibernate Validator案例

  1. 项目结构
  2. Maven 依赖
  3. 基础实体类示例
  4. 分组验证示例
  5. 自定义验证器
  6. 交叉字段验证示例
  7. 服务层验证示例
  8. 控制器和异常处理
  9. 工具类和测试示例
  10. 完整配置(使用 Spring Boot)
  11. 高级验证示例
  12. 关键要点

我来通过一个完整的案例演示 Hibernate Validator 的使用。

项目结构

demo/
├── pom.xml
└── src/
    └── main/
        └── java/
            └── com/
                └── example/
                    ├── User.java
                    ├── UserService.java
                    ├── UserController.java
                    ├── CustomValidator.java
                    └── ValidatorUtil.java

Maven 依赖

<dependencies>
    <!-- Hibernate Validator -->
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>8.0.1.Final</version>
    </dependency>
    <!-- 表达式语言依赖 -->
    <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>jakarta.el</artifactId>
        <version>4.0.2</version>
    </dependency>
    <!-- 实体类如需要使用注解 -->
    <dependency>
        <groupId>jakarta.persistence</groupId>
        <artifactId>jakarta.persistence-api</artifactId>
        <version>3.1.0</version>
    </dependency>
    <!-- Json 处理(用于 Controller 示例) -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version>
    </dependency>
</dependencies>

基础实体类示例

package com.example;
import jakarta.validation.constraints.*;
import jakarta.validation.constraints.Pattern.Flag;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
/**
 * 用户实体类,展示各种常用约束
 */
public class User {
    @NotNull(message = "用户ID不能为空")
    @Min(value = 1, message = "用户ID必须大于0")
    @Max(value = 99999, message = "用户ID不能超过99999")
    private Long id;
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
    private String username;
    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 32, message = "密码长度必须在6-32之间")
    @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-zA-Z]).{6,32}$", 
             message = "密码必须同时包含字母和数字")
    private String password;
    @Email(message = "邮箱格式不正确")
    @NotBlank(message = "邮箱不能为空")
    private String email;
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
    @Min(value = 0, message = "年龄不能为负数")
    @Max(value = 150, message = "年龄不能超过150")
    private Integer age;
    @DecimalMin(value = "0.0", message = "工资不能为负数")
    @DecimalMax(value = "999999.99", message = "工资不能超过999999.99")
    @Digits(integer = 6, fraction = 2, message = "工资格式不正确")
    private BigDecimal salary;
    @NotNull(message = "出生日期不能为空")
    @Past(message = "出生日期必须是过去的日期")
    private LocalDate birthDate;
    @Future(message = "到期日期必须是未来的日期")
    private LocalDate expiryDate;
    @AssertTrue(message = "必须同意用户协议")
    private boolean agree;
    @NotEmpty(message = "角色列表不能为空")
    @Size(min = 1, max = 5, message = "角色数量必须在1-5之间")
    private List<@NotBlank(message = "角色不能为空") String> roles;
    // getters and setters...
}

分组验证示例

package com.example;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import jakarta.validation.groups.Default;
/**
 * 使用分组验证的用户类
 */
public class GroupUser {
    // 定义分组接口
    public interface AddGroup {}
    public interface UpdateGroup {}
    public interface QueryGroup {}
    @NotNull(groups = UpdateGroup.class, message = "更新时ID不能为空")
    @Null(groups = AddGroup.class, message = "新增时ID必须为空")
    private Long id;
    @NotBlank(groups = {AddGroup.class, UpdateGroup.class}, 
              message = "用户名不能为空")
    @Size(min = 3, max = 20, 
          groups = {AddGroup.class, UpdateGroup.class, QueryGroup.class},
          message = "用户名长度必须在3-20之间")
    private String username;
    @NotBlank(groups = AddGroup.class, message = "新增时密码不能为空")
    @Size(min = 6, max = 32, groups = AddGroup.class,
          message = "密码长度必须在6-32之间")
    private String password;
    @NotBlank(groups = {AddGroup.class, UpdateGroup.class},
              message = "邮箱不能为空")
    @Email(groups = Default.class, message = "邮箱格式不正确")
    private String email;
    // 自定义验证方法(分组方法级验证)
    @AssertTrue(groups = AddGroup.class, message = "新增时必须是活跃用户")
    public boolean isActive() {
        return active;
    }
    private boolean active;
    // getters and setters...
}

自定义验证器

package com.example;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 自定义注解
@Documented
@Constraint(validatedBy = PasswordValidator.class)
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPassword {
    String message() default "密码格式不正确,需要包含大小写字母和数字";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    // 参数:是否必须包含大小写
    boolean requireUppercase() default true;
    boolean requireLowercase() default true;
    boolean requireDigit() default true;
    int minLength() default 6;
    int maxLength() default 32;
}
// 验证器实现
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
    private boolean requireUppercase;
    private boolean requireLowercase;
    private boolean requireDigit;
    private int minLength;
    private int maxLength;
    @Override
    public void initialize(ValidPassword constraintAnnotation) {
        this.requireUppercase = constraintAnnotation.requireUppercase();
        this.requireLowercase = constraintAnnotation.requireLowercase();
        this.requireDigit = constraintAnnotation.requireDigit();
        this.minLength = constraintAnnotation.minLength();
        this.maxLength = constraintAnnotation.maxLength();
    }
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return false;
        }
        // 密码长度检查
        if (value.length() < minLength || value.length() > maxLength) {
            return false;
        }
        // 必须包含大写字母
        if (requireUppercase && !value.matches(".*[A-Z].*")) {
            return false;
        }
        // 必须包含小写字母
        if (requireLowercase && !value.matches(".*[a-z].*")) {
            return false;
        }
        // 必须包含数字
        if (requireDigit && !value.matches(".*\\d.*")) {
            return false;
        }
        // 禁止特殊字符
        if (value.matches(".*[^a-zA-Z0-9].*")) {
            return false;
        }
        return true;
    }
}

交叉字段验证示例

package com.example;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.Payload;
import java.lang.annotation.*;
// 自定义类级验证注解 - 检查密码确认
@Documented
@Constraint(validatedBy = PasswordMatchValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PasswordMatch {
    String message() default "密码和确认密码不一致";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String passwordField() default "password";
    String confirmPasswordField() default "confirmPassword";
}
// 实现类
public class PasswordMatchValidator 
    implements ConstraintValidator<PasswordMatch, Object> {
    private String passwordField;
    private String confirmPasswordField;
    @Override
    public void initialize(PasswordMatch constraintAnnotation) {
        this.passwordField = constraintAnnotation.passwordField();
        this.confirmPasswordField = constraintAnnotation.confirmPasswordField();
    }
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        try {
            // 通过反射获取字段值
            java.lang.reflect.Field passwordField = 
                value.getClass().getDeclaredField(passwordField);
            passwordField.setAccessible(true);
            java.lang.reflect.Field confirmPasswordField = 
                value.getClass().getDeclaredField(confirmPasswordField);
            confirmPasswordField.setAccessible(true);
            String password = (String) passwordField.get(value);
            String confirmPassword = (String) confirmPasswordField.get(value);
            // 两个都为 null 或都不为 null 且相等
            if (password == null && confirmPassword == null) {
                return true;
            }
            if (password != null && password.equals(confirmPassword)) {
                return true;
            }
            // 构建错误信息
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(
                "密码和确认密码不一致"
            ).addPropertyNode(confirmPasswordField).addConstraintViolation();
            return false;
        } catch (Exception e) {
            return false;
        }
    }
}
// 使用示例
@PasswordMatch(passwordField = "password", confirmPasswordField = "confirmPassword")
public class RegistrationForm {
    @NotBlank(message = "用户名不能为空")
    private String username;
    @ValidPassword(minLength = 8, maxLength = 20, 
                   requireUppercase = true, requireLowercase = true, requireDigit = true)
    private String password;
    private String confirmPassword;
    @Email(message = "邮箱格式不正确")
    private String email;
    // getters and setters...
}

服务层验证示例

package com.example;
import jakarta.validation.Valid;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.constraints.*;
import java.util.Set;
/**
 * 服务层使用编程式验证
 */
public class UserService {
    private final Validator validator;
    public UserService() {
        // 创建一个验证器
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        this.validator = factory.getValidator();
    }
    /**
     * 编程式验证示例
     */
    public void addUser(User user) throws ValidationException {
        // 执行验证(使用默认分组)
        Set<ConstraintViolation<User>> violations = validator.validate(user);
        if (!violations.isEmpty()) {
            throw new ValidationException(buildErrorMessage(violations));
        }
        // 业务逻辑...
        System.out.println("用户添加成功: " + user.getUsername());
    }
    /**
     * 指定分组验证
     */
    public void updateUser(GroupUser user) throws ValidationException {
        // 使用 UpdateGroup 分组进行验证
        Set<ConstraintViolation<GroupUser>> violations = 
            validator.validate(user, GroupUser.UpdateGroup.class);
        if (!violations.isEmpty()) {
            throw new ValidationException(buildErrorMessage(violations));
        }
        // 业务逻辑...
        System.out.println("用户更新成功");
    }
    /**
     * 方法参数验证(需要配合 AOP 或 Spring)
     */
    public void updateUserById(
            @NotNull(groups = GroupUser.UpdateGroup.class) Long id,
            @Valid GroupUser user) {
        // 这种方法级验证通常由 Spring AOP 处理
        System.out.println("更新用户 " + id + " 的信息");
    }
    /**
     * 构建错误信息
     */
    private String buildErrorMessage(
            Set<? extends ConstraintViolation<?>> violations) {
        StringBuilder sb = new StringBuilder("验证失败: ");
        violations.forEach(v -> 
            sb.append(v.getPropertyPath())
              .append(": ")
              .append(v.getMessage())
              .append("; ")
        );
        return sb.toString();
    }
    /**
     * 验证单个属性
     */
    public void validateField(String fieldName, Object value) {
        Set<? extends ConstraintViolation<?>> violations = 
            validator.validateValue(User.class, fieldName, value);
        if (!violations.isEmpty()) {
            throw new ValidationException(buildErrorMessage(violations));
        }
    }
}

控制器和异常处理

package com.example;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * Spring MVC 控制器示例(需要 Spring 环境)
 */
@RestController
@RequestMapping("/api/users")
public class UserController {
    private final UserService userService;
    public UserController(UserService userService) {
        this.userService = userService;
    }
    /**
     * 创建用户 - 使用 @Valid 注解
     */
    @PostMapping
    public ResponseEntity<?> createUser(@Valid @RequestBody User user) {
        try {
            userService.addUser(user);
            return ResponseEntity.ok(Map.of(
                "message", "用户创建成功",
                "user", user
            ));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(Map.of(
                "error", e.getMessage()
            ));
        }
    }
    /**
     * 更新用户 - 使用 @Validated 和分组
     */
    @PutMapping("/{id}")
    public ResponseEntity<?> updateUser(@PathVariable Long id, 
                                        @Validated @RequestBody GroupUser user) {
        user.setId(id);
        userService.updateUser(user);
        return ResponseEntity.ok(Map.of("message", "更新成功"));
    }
    /**
     * 异常处理 - 处理验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handleValidationExceptions(
            MethodArgumentNotValidException ex) {
        Map<String, String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .collect(Collectors.toMap(
                error -> error.getField(),
                error -> error.getDefaultMessage() != null ? 
                         error.getDefaultMessage() : "Invalid value"
            ));
        return ResponseEntity.badRequest().body(Map.of(
            "status", HttpStatus.BAD_REQUEST.value(),
            "errors", errors
        ));
    }
    /**
     * 异常处理 - 处理约束违规异常
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<?> handleConstraintViolation(
            ConstraintViolationException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getConstraintViolations().forEach(v -> {
            String field = v.getPropertyPath().toString();
            errors.put(field, v.getMessage());
        });
        return ResponseEntity.badRequest().body(Map.of(
            "status", HttpStatus.BAD_REQUEST.value(),
            "errors", errors
        ));
    }
}

工具类和测试示例

package com.example;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import jakarta.validation.metadata.BeanDescriptor;
import jakarta.validation.metadata.PropertyDescriptor;
import java.util.Set;
/**
 * 验证工具类
 */
public class ValidatorUtil {
    private static final Validator validator;
    static {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }
    /**
     * 验证对象(默认分组)
     */
    public static <T> ValidationResult validate(T object) {
        Set<ConstraintViolation<T>> violations = validator.validate(object);
        return new ValidationResult(violations);
    }
    /**
     * 验证对象(指定分组)
     */
    public static <T> ValidationResult validate(T object, Class<?>... groups) {
        Set<ConstraintViolation<T>> violations = 
            validator.validate(object, groups);
        return new ValidationResult(violations);
    }
    /**
     * 验证单个属性
     */
    public static ValidationResult validateProperty(
            Object object, String propertyName) {
        Set<ConstraintViolation<Object>> violations = 
            validator.validateProperty(object, propertyName);
        return new ValidationResult(violations);
    }
    /**
     * 获取类的元数据
     */
    public static void printClassMetadata(Class<?> clazz) {
        BeanDescriptor beanDescriptor = validator.getConstraintsForClass(clazz);
        System.out.println("类 " + clazz.getSimpleName() + " 是否包含约束: " 
            + beanDescriptor.isBeanConstrained());
        for (PropertyDescriptor property : beanDescriptor.getConstrainedProperties()) {
            System.out.println("属性: " + property.getPropertyName() + 
                " 是否有级联验证: " + property.isCascaded());
        }
    }
    /**
     * 验证结果类
     */
    public static class ValidationResult {
        private final Set<? extends ConstraintViolation<?>> violations;
        public ValidationResult(Set<? extends ConstraintViolation<?>> violations) {
            this.violations = violations;
        }
        public boolean isValid() {
            return violations.isEmpty();
        }
        public String getErrorMessages() {
            if (isValid()) {
                return "";
            }
            return violations.stream()
                .map(v -> v.getPropertyPath() + ": " + v.getMessage())
                .collect(java.util.stream.Collectors.joining("; "));
        }
        public Set<? extends ConstraintViolation<?>> getViolations() {
            return violations;
        }
    }
}
// 测试主类
public class Main {
    public static void main(String[] args) {
        // 创建用户对象进行测试
        User user = new User();
        user.setUsername("John Doe");      // 用户名包含非法字符
        user.setPassword("123");           // 密码太短
        user.setEmail("invalid-email");    // 邮箱格式不正确
        // 使用工具类验证
        ValidatorUtil.ValidationResult result = 
            ValidatorUtil.validate(user);
        if (!result.isValid()) {
            System.out.println("验证失败: " + result.getErrorMessages());
        }
        // 验证单个属性
        ValidatorUtil.ValidationResult emailResult = 
            ValidatorUtil.validateProperty(user, "email");
        System.out.println("邮箱验证: " + 
            (emailResult.isValid() ? "通过" : "失败 - " + emailResult.getErrorMessages()));
        // 查看类元数据
        ValidatorUtil.printClassMetadata(User.class);
    }
}

完整配置(使用 Spring Boot)

package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import jakarta.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    @Configuration
    public static class ValidatorConfig {
        @Bean
        public Validator validator() {
            return new LocalValidatorFactoryBean();
        }
        @Bean
        public jakarta.validation.ValidatorFactory validatorFactory() {
            return jakarta.validation.Validation.buildDefaultValidatorFactory();
        }
    }
}

高级验证示例

package com.example;
import jakarta.validation.constraints.*;
import java.util.List;
import java.util.Optional;
/**
 * 高级验证特性示例
 */
public class AdvancedValidation {
    // 1. 嵌套对象验证
    public static class Order {
        @NotNull(message = "订单ID不能为空")
        private Long orderId;
        @Valid  // 级联验证
        private Customer customer;
        @Valid
        @NotEmpty(message = "订单项不能为空")
        private List<@Valid OrderItem> items;
    }
    public static class Customer {
        @NotBlank(message = "客户名不能为空")
        private String name;
        @Email(message = "客户邮箱格式不正确")
        private String email;
    }
    public static class OrderItem {
        @NotBlank(message = "商品名称不能为空")
        private String productName;
        @Positive(message = "商品数量必须为正数")
        private Integer quantity;
        @PositiveOrZero(message = "商品价格不能为负数")
        private Double price;
    }
    // 2. Optional 验证
    public static class OptionalDemo {
        @NotBlank(message = "Optional 值不能为空")
        private Optional<String> optionalString;
        @Positive(message = "Optional 数值必须为正数")
        private Optional<Integer> optionalInteger;
    }
    // 3. 条件验证
    public static class ConditionalValidation {
        private String type;
        @NotNull(groups = TypeA.class, message = "类型A必须指定")
        private String typeAAttribute;
        @NotNull(groups = TypeB.class, message = "类型B必须指定")
        private String typeBAttribute;
        // 分组接口
        public interface TypeA {}
        public interface TypeB {}
        // 自定义组序列
        @jakarta.validation.GroupSequence
        public interface ValidationOrder extends TypeA, TypeB {}
    }
    // 4. EL 表达式验证
    public static class ELValidation {
        @AssertTrue(message = "促销价格必须小于原价")
        public boolean isPromotionValid() {
            return originalPrice.compareTo(salePrice) > 0;
        }
        private java.math.BigDecimal originalPrice;
        private java.math.BigDecimal salePrice;
    }
    // 5. 自定义约束组合
    @Target({java.lang.annotation.ElementType.FIELD})
    @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
    @NotBlank(message = "电话不能为空")
    @Pattern(regexp = "^[0-9-\\s]+$", message = "电话格式不正确")
    public @interface ValidPhone {}
}

关键要点

  1. 常用约束注解

    • @NotNull@NotBlank@NotEmpty:空值检查
    • @Size@Min@Max:大小检查
    • @Pattern:正则表达式验证
    • @Email:邮箱格式验证
    • @Past@Future:日期验证
    • @Valid:级联验证
  2. 分组验证:不同场景使用不同验证规则

  3. 自定义验证器:扩展框架功能

  4. 交叉字段验证:确保字段间的约束关系

  5. Spring Boot 集成:自动配置简化开发

  6. 异常处理:统一处理验证异常

这个案例覆盖了 Hibernate Validator 的主要功能,可以根据项目需求选择使用相应的功能。

抱歉,评论功能暂时关闭!