服务层中的验证(SpringBoot)

时间:2019-05-06 22:17:21

标签: java hibernate spring-boot validation design-patterns

我有一个DTO,它在Controller层通过BeanValidation(javax.validation)和自定义Validator(org.springframework.validation.Validator)的混合进行了验证。这样,我可以检查提供的输入是否有效,然后将DTO转换为实体并将其转发到Service层。

    @Data
    public class UserDTO {

            @NotBlank
            @Size(max = 25)
            private String name;

            @NotNull
            private Date birthday;

            @NotNull
            private Date startDate;

            private Date endDate;

            private Long count;

    }

    public class UserDTOValidator implements Validator {
        private static final String START_DATE= "startDate";
        private static final String END_DATE= "endDate";
        private static final String COUNT= "count";

        @Override
        public boolean supports(Class<?> clazz) {
            return UserDTO.class.isAssignableFrom(clazz);
        }
        @Override
        public void validate(Object target, Errors errors) {

            UserDTO vm = (UserDTO) target;

            if (vm.getEndDate() != null) {
               if (vm.getStartDate().after(vm.getEndDate())) {
                errors.rejectValue(START_DATE, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
               }

               if (vm.getEndDate().equals(vm.getStartDate()) || vm.getEndDate().before(vm.getStartDate())) {
                errors.rejectValue(END_DATE, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
               }
            }

            if (vm.getCount() < 1) {
             errors.rejectValue(COUNT, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
            }
            .....

        }

    }


public class UserController {
    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(new UserDTOValidator());
    }
    @PostMapping()
    public ResponseEntity<UserDTO> create(@RequestBody @Valid UserDTO userDTO) {
       .....
    }
    .....
}

然后是业务逻辑验证。例如:@Entity用户的startDate必须在某个事件发生之后,并且如果最后创建的用户的生日是在Summer,则计数必须大于X,否则,该实体应由用户服务丢弃。

@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private SomeEventService someEventService ;

    @Override
    public User create(User entity) {
        String error = this.validateUser(entity);
        if (StringUtils.isNotBlank(error)) {
            throw new ValidationException(error);
        }

        return this.userRepository.save(entity);
    }
    ....
    private String validateUser(User entity) {
        SomeEvent someEvent = this.someEventService.get(entity.getName()); 
        if (entity.getStartDate().before(someEvent.getDate())) {
            return "startDate";
        }
        User lastUser = this.userRepository.findLast();
        ....
    }

}

但是我觉得这不是处理业务逻辑验证的最佳方法。我该怎么办? ConstraintValidator / HibernateValidator / JPA事件侦听器?它们可以在@Entity类级别工作,还是我必须为每个不同的字段检查创建X个?你们如何在实际的生产应用程序中做到这一点?

1 个答案:

答案 0 :(得分:1)

在我的建议下,

  1. 通过@Valid使用经典的字段级验证

样本

void myservicemethod(@Valid UserDTO user)
  1. 要在实体级别进行自定义业务级别验证,请在DTO本身中创建validate方法

样本

class UserDTO {
    //fields and getter setter
    void validate() throws ValidationException {
        //your entity level business logic
    }
}

此策略将有助于将特定于实体的验证逻辑保留在实体中

  1. 如果仍然有需要其他服务调用的验证逻辑,请使用自定义ConstraintValidator(例如question on stackoverflow)创建自定义验证注释。在这种情况下,我的首选是从此自定义验证器调用UserDTO.validate(),以实现从服务调用的目的

这将有助于使您的验证逻辑与服务层分离,并且可移植和模块化