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

如何避免验证DTO与Entity时Jakarta校验注解重复?

避免DTO与JPA实体校验规则重复的方案

在Spring项目中,DTO和JPA实体的校验注解重复会增加维护成本,以下几种方案可遵循DRY原则解决该问题:

方案一:自定义组合校验注解

将重复的校验规则封装为一个自定义注解,在实体和DTO中统一使用该注解,后续只需修改自定义注解的规则即可同步所有使用场景。

实现步骤:

  1. 定义包含组合校验规则的自定义注解:
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import jakarta.validation.constraints.NotEmpty;
import java.lang.annotation.*;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {}) // 无需自定义校验器,直接组合现有注解
@NotEmpty // 可添加多个校验注解,如@Size(min=2, max=50)
public @interface ProductName {
    String message() default "商品名称不能为空";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
  1. 在实体和DTO中使用该注解:
@Entity
@Table
class Product {
    @ProductName
    private String name;
    // id、getter、setter等
}
class NewProductRequest {
    @ProductName
    public String name;
}

方案二:自定义校验器复用实体校验逻辑

编写Spring MVC自定义校验器,将DTO字段映射到实体后复用实体的校验规则,无需在DTO上添加重复注解。

实现步骤:

  1. 实现自定义校验器:
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Set;

@Component
public class NewProductRequestValidator implements org.springframework.validation.Validator {

    private final jakarta.validation.Validator beanValidator;

    @Autowired
    public NewProductRequestValidator(Validator beanValidator) {
        this.beanValidator = beanValidator;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return NewProductRequest.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        NewProductRequest request = (NewProductRequest) target;
        // 将DTO转换为实体对象
        Product product = new Product();
        product.setName(request.getName());
        
        // 复用实体的校验规则
        Set<ConstraintViolation<Product>> violations = beanValidator.validate(product);
        // 将校验结果转换为Spring Errors格式
        for (ConstraintViolation<Product> violation : violations) {
            String field = violation.getPropertyPath().toString();
            errors.rejectValue(field, null, violation.getMessage());
        }
    }
}
  1. 在控制器中绑定校验器:
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;

@RestController
@RequestMapping("/products")
public class ProductController {

    @Autowired
    private NewProductRequestValidator requestValidator;

    @InitBinder("newProductRequest")
    public void initBinder(WebDataBinder binder) {
        binder.addValidators(requestValidator);
    }

    @PostMapping
    public ResponseEntity<?> createProduct(@Validated @RequestBody NewProductRequest request) {
        // 业务逻辑处理
        return ResponseEntity.ok().build();
    }
}

方案三:映射工具结合实体校验

借助MapStruct、ModelMapper等映射工具,将DTO转换为实体后直接校验实体,无需在DTO上添加校验注解。

示例(使用MapStruct):

  1. 定义映射接口:
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring")
public interface ProductMapper {
    Product toEntity(NewProductRequest request);
}
  1. 在控制器中转换并校验:
import jakarta.validation.Validator;
import jakarta.validation.ConstraintViolation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Set;

@RestController
@RequestMapping("/products")
public class ProductController {

    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private Validator validator;

    @PostMapping
    public ResponseEntity<?> createProduct(@RequestBody NewProductRequest request) {
        Product product = productMapper.toEntity(request);
        // 校验实体
        Set<ConstraintViolation<Product>> violations = validator.validate(product);
        if (!violations.isEmpty()) {
            // 处理校验错误,如抛出MethodArgumentNotValidException
            throw new IllegalArgumentException("参数校验失败");
        }
        // 保存实体
        productRepository.save(product);
        return ResponseEntity.ok().build();
    }
}

方案四:校验组区分场景(适用于部分规则复用)

通过定义校验组,让实体和DTO共用同一组规则,同时区分不同业务场景(如创建/更新)的校验要求。

实现步骤:

  1. 定义校验组接口:
public interface CreateGroup {}
public interface UpdateGroup {}
  1. 在实体中指定字段的校验组:
@Entity
@Table
class Product {
    @Null(groups = CreateGroup.class)
    @NotNull(groups = UpdateGroup.class)
    private Long id;

    @NotEmpty(groups = {CreateGroup.class, UpdateGroup.class})
    private String name;
}
  1. 在DTO和控制器中使用校验组:
class NewProductRequest {
    @NotEmpty(groups = CreateGroup.class)
    public String name;
}
@PostMapping
public ResponseEntity<?> createProduct(@Validated(CreateGroup.class) @RequestBody NewProductRequest request) {
    // 业务逻辑处理
}

内容的提问来源于stack exchange,提问作者Ruslan K.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 08:10:28