복붙노트

[SPRING] 추가 정보와 콩 검증

SPRING

추가 정보와 콩 검증

내가 만드는 프로젝트 API에 대한 사용자 정의 빈 검증 주석과 같은 고유 한 이름 주석을 만들려고하고있다 :

@PostMapping("/users/{userId}/projects")
public ResponseEntity createNewProject(@PathVariable("userId") String userId,
                                       @RequestBody @Valid ProjectParam projectParam) {
    User projectOwner = userRepository.ofId(userId).orElseThrow(ResourceNotFoundException::new);

    Project project = new Project(
        IdGenerator.nextId(),
        userId,
        projectParam.getName(),
        projectParam.getDescription()
    );
    ...
  }

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
class ProjectParam {

  @NotBlank
  @NameConstraint
  private String name;
  private String description;
}

@Constraint(validatedBy = UniqueProjectNameValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface UniqueName {

    public String message() default "already existed";

    public Class<?>[] groups() default {};

    public Class<? extends Payload>[] payload() default{};
}

public class UniqueProjectNameValidator implements ConstraintValidator<UniqueName, String> {
   @Autowired
   private ProjectQueryMapper mapper;

   public void initialize(UniqueName constraint) {
   }

   public boolean isValid(String value, ConstraintValidatorContext context) {
      // how can I get the userId info??
      return mapper.findByName(userId, value) == null;
   }
}

문제는 이름 필드는 단지 사용자 수준에 대한 고유성 필요가 있다는 것입니다. 그래서 검증을 위해 URL 필드에서 {사용자 ID} 얻을 필요가있다. 하지만 내가 어떻게 UniqueProjectNameValidator에이를 추가 할 수 있습니까? 또는이 검증을 처리하는 더 좋은 방법은 무엇입니까? 이것은 대형 오브젝트의 단지 작은 부분, 실제 개체 코드가 더러워 수 있도록 요청 처리기에서 다른 많은 복잡한 검증 있습니다.

해결법

  1. ==============================

    1.@Abhijeet가 언급 한 바와 같이, 동적 제약 검증에 사용자 ID 속성을 통과하는 것은 불가능하다. 이 검증 경우 더 나은 처리하는 방법에 관해서는, 깨끗한 솔루션과 더러운 솔루션이있다.

    @Abhijeet가 언급 한 바와 같이, 동적 제약 검증에 사용자 ID 속성을 통과하는 것은 불가능하다. 이 검증 경우 더 나은 처리하는 방법에 관해서는, 깨끗한 솔루션과 더러운 솔루션이있다.

    깨끗한 솔루션은 서비스 메서드에 모든 비즈니스 로직을 추출하고, 서비스 수준에서 ProjectParam을 확인하는 것입니다. 이 방법을 사용하면 ProjectParam에 사용자 ID 속성을 추가하고, 서비스를 호출하기 전에 @RequestBody 상 @PathVariable에서 매핑 할 수 있습니다. 그러면 오히려 문자열보다 ProjectParams의 유효성을 검사 UniqueProjectNameValidator을 조정합니다.

    더러운 솔루션은 최대 절전 모드 검사기의 교차 매개 변수 제약 (또한 예를 들어,이 링크를 참조)를 사용하는 것입니다. 당신은 기본적으로 사용자 정의 유효성 검사기에 대한 입력으로 컨트롤러 메서드 매개 변수를 모두 취급합니다.

  2. ==============================

    2.내가 잘못 아니에요 경우 통과 userId를위한 기존 projectNames에 프로젝트 이름 필드의 유효성을 검사하는 사용자 ID에 액세스 할 수 있도록, 당신이 요구하는 무엇을, 어떻게 즉 @UniqueName 사용자 정의 주석에 사용자 ID를 전달할 수 있습니다.

    내가 잘못 아니에요 경우 통과 userId를위한 기존 projectNames에 프로젝트 이름 필드의 유효성을 검사하는 사용자 ID에 액세스 할 수 있도록, 당신이 요구하는 무엇을, 어떻게 즉 @UniqueName 사용자 정의 주석에 사용자 ID를 전달할 수 있습니다.

    그것은 당신이 동적으로 가능하지 않다 주석에 변수 / 매개 변수를 전달하는 방법에 관한 것입니다 요구하는 것을 의미한다. 당신은 인터셉터와 같은 다른 방법을 사용하거나 수동으로 유효성 검사를 수행해야합니다.

    당신은뿐만 아니라 다음과 같은 답변을 참조 할 수 있습니다 :

    어떻게 자바에서 사용자 지정 주석에 가치를 전달하는 방법?

    주석에 동적 매개 변수를 전달?

  3. ==============================

    3.Dyakonov @Mikhail이 문서에서 자바를 사용하여 최적의 검증 방법을 선택하는 엄지 손가락의 규칙을 제안했다 :

    Dyakonov @Mikhail이 문서에서 자바를 사용하여 최적의 검증 방법을 선택하는 엄지 손가락의 규칙을 제안했다 :

    나는 엔티티 리스너 내에서 당신은 / 지속을 업데이트하고 수표 쿼리를 실행하기 쉽게하기 전에 JPA 엔티티를 액세스 할 수 있기 때문에 엔티티 리스너들, 당신의 고유 제한 조건 검증 문제를 일치합니다.

    @crizzis 나를 지적 그러나,이 방법에 상당한 제한이있다. JPA 2 스펙 (JSR 317)에 명시된 바와 같이 :

    이 방법을 시도하든, 먼저 현재의 EntityManager 인스턴스를 얻기위한 ApplicationContextAware 구현이 필요합니다. 그것은 어쩌면 당신은 이미 그것을 사용하고, 오래된 스프링 프레임 워크의 트릭입니다.

    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    @Component
    public final class BeanUtil implements ApplicationContextAware {
    
       private static ApplicationContext CONTEXT;
    
            @Override
            public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
                CONTEXT = applicationContext;
            }
    
            public static <T> T getBean(Class<T> beanClass) {
                return CONTEXT.getBean(beanClass);
            }    
        }
    

    이것은 내 엔티티 리스너입니다

    @Slf4j
    public class GatewaUniqueIpv4sListener { 
    
        @PrePersist
        void onPrePersist(Gateway gateway) {       
           try {
               EntityManager entityManager = BeanUtil.getBean(EntityManager.class);
               Gateway entity = entityManager
                    .createQuery("SELECT g FROM Gateway g WHERE g.ipv4 = :ipv4", Gateway.class)
                    .setParameter("ipv4", gateway.getIpv4())
                    .getSingleResult();
    
               // Already exists a Gateway with the same Ipv4 in the Database or the PersistenceContext
               throw new IllegalArgumentException("Can't be to gateways with the same Ip address " + gateway.getIpv4());
           } catch (NoResultException ex) {
               log.debug(ex.getMessage(), ex);
           }
        }
    }
    

    마지막으로, 나는 내 엔티티 클래스를 @EntityListener이 주석 (GatewaUniqueIpv4sListener.class를) 추가

    당신은 완전한 여기에 작업 코드 게이트웨이 - 자바를 찾을 수 있습니다

    깨끗한 간단한 방법은 당신이 당신의 트랜잭션 서비스 내에서 데이터베이스에 액세스 할 필요가있는 검증을 확인할 수 있습니다. 심지어 당신이 더 나은 솔루션을 구현하기 위해 책임 패턴의 사양, 전략, 및 체인을 사용할 수 있습니다.

  4. ==============================

    4.난 당신이 요구하는지 무엇을 할 수 있다고 생각하지만, 당신은 당신의 접근 방식 조금을 일반화해야 할 수도 있습니다.

    난 당신이 요구하는지 무엇을 할 수 있다고 생각하지만, 당신은 당신의 접근 방식 조금을 일반화해야 할 수도 있습니다.

    다른 언급으로 대신 필드 수준 유효성 검사기의 클래스 수준 검사기로 귀하의 유효성 검사기를 변경 한 경우, 당신은 발리로 두 가지 속성을 통과 할 수는 없지만,이 작업을 수행 할 수 있습니다.

    여기에 우리가 제출 될 때 두 개의 필드가 같은 값 있는지 확인하게 만든 검사기입니다. 암호의 생각과 자주 웹 사이트 또는 이메일을 참조 암호 사용 사례를 확인하고 이메일 사용 사례를 확인합니다.

    물론, 특정 경우에, 당신은 사용자의 ID와 자신이 만든 것을 시도하고있는 프로젝트의 이름을 전달해야합니다.

    주석:

    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    
    import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
    import static java.lang.annotation.ElementType.TYPE;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    
    /**
     * Taken from:
     * http://stackoverflow.com/questions/1972933/cross-field-validation-with-hibernate-validator-jsr-303
     * <p/>
     * Validation annotation to validate that 2 fields have the same value.
     * An array of fields and their matching confirmation fields can be supplied.
     * <p/>
     * Example, compare 1 pair of fields:
     *
     * @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match")
     * <p/>
     * Example, compare more than 1 pair of fields:
     * @FieldMatch.List({
     * @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
     * @FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")})
     */
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Constraint(validatedBy = FieldMatchValidator.class)
    @Documented
    public @interface FieldMatch {
        String message() default "{constraints.fieldmatch}";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
        /**
         * @return The first field
         */
        String first();
    
        /**
         * @return The second field
         */
        String second();
    
        /**
         * Defines several <code>@FieldMatch</code> annotations on the same element
         *
         * @see FieldMatch
         */
        @Target({TYPE, ANNOTATION_TYPE})
        @Retention(RUNTIME)
        @Documented
        @interface List {
            FieldMatch[] value();
        }
    }
    

    유효성 검사기 :

    import org.apache.commons.beanutils.BeanUtils;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    /**
     * Taken from:
     * http://stackoverflow.com/questions/1972933/cross-field-validation-with-hibernate-validator-jsr-303
     */
    public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
        private String firstFieldName;
        private String secondFieldName;
    
        @Override
        public void initialize(FieldMatch constraintAnnotation) {
    
            firstFieldName = constraintAnnotation.first();
            secondFieldName = constraintAnnotation.second();
        }
    
        @Override
        public boolean isValid(Object value, ConstraintValidatorContext context) {
    
            try {
                Object firstObj = BeanUtils.getProperty(value, firstFieldName);
                Object secondObj = BeanUtils.getProperty(value, secondFieldName);
    
                return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
            } catch (Exception ignore) {
                // ignore
            }
            return true;
        }
    }
    

    그리고 우리의 명령 개체를 여기서는

    import org.hibernate.validator.constraints.Length;
    import org.hibernate.validator.constraints.NotBlank;
    
    import javax.validation.GroupSequence;
    
    @GroupSequence({Required.class, Type.class, Data.class, Persistence.class, ChangePasswordCommand.class})
    @FieldMatch(groups = Data.class, first = "password", second = "confirmNewPassword", message = "The New Password and Confirm New Password fields must match.")
    public class ChangePasswordCommand {
    
        @NotBlank(groups = Required.class, message = "New Password is required.")
        @Length(groups = Data.class, min = 6, message = "New Password must be at least 6 characters in length.")
        private String password;
    
        @NotBlank(groups = Required.class, message = "Confirm New Password is required.")
        private String confirmNewPassword;
    
        ...
    }
    
  5. from https://stackoverflow.com/questions/56916520/bean-validation-with-extra-information by cc-by-sa and MIT license