본문 바로가기
프로그래밍/스프링프레임워크

Hibernate Bean Validator 사용하기(2) - 그룹과 커스텀 아노테이션

by pentode 2018. 4. 21.

앞에서의 기본 사용법과 이어지는 내용으로 다음과 같은 내용을 알아보겠습니다. 예제의 전체 소스는 글의 끝에 첨부해 두었습니다.

 

- 빈 객체가 멤버로 객체를 가지거나 컬렉션을 가질 경우 모두 검증하기

- 객체의 멤버들을 그룹을 지정하여 특정상황에 특정 멤버만 검증하기

- 커스텀 검증 아노테이션 만들기

 

 

1. 테스트용 폼 만들기(personForm.jsp)

<c:url var="insertUrl" value="/personInsert.do" />

<form:form commandName="personVO" action="${insertUrl}" name="personVO" method="post">

...

  <tr>

    <th>주민번호</th>

    <td>

      <form:input path="jumin" size="20" maxlength="14" />

      <form:errors path="jumin" />

    </td>

  </tr>

  <tr>

    <th>비밀번호</th>

    <td>

      <form:password path="password" size="20" maxlength="20" />

      <form:errors path="password" />

    </td>

  </tr>

...

  </tr>

    <td>

      <form:input path="relations[0].relation" size="20" maxlength="20" /><br />

      <form:errors path="relations[0].relation" />

    </td>

    <td>

      <form:input path="relations[0].name" size="20" maxlength="20" /><br />

      <form:errors path="relations[0].name" />

    </td>

    <td>

      <form:input path="relations[0].email" size="20" maxlength="20" /><br />

      <form:errors path="relations[0].email" />

     </td>

  </tr>

...

  <tr>

    <td>

      <form:input path="recommender.relation" size="20" maxlength="20" /><br />

      <form:errors path="recommender.relation" />

    </td>

    <td>

      <form:input path="recommender.name" size="20" maxlength="20" /><br />

      <form:errors path="recommender.name" />

    </td>

    <td>

      <form:input path="recommender.email" size="20" maxlength="20" /><br />

      <form:errors path="recommender.email" />

    </td>

  </tr>

...

</form:form>

 

- 주민번호는 검증을 위한 커스텀 아노테이션을 만들어 봅니다.

- 비밀번호는 그룹을 사용하여 특정 상황에서만 검증하도록 하여 봅니다.

- relations[0] 으로 된 부분은 멤버로 객체 리스트를 가지는 부분으로 리스트 전체를 검증 합니다.

- recommender 부분은 멤버로 가지는 하나의 객체의 멤버를 검증하도록 합니다.

 

 

2. VO에 제약사항 지정하기

 

- 그룹 지정은 이름만 가지는 인터페이스로 처리 합니다.(PersonGroupA.java)

package com.tistory.pentode.vo;

 

public interface PersonGroupA { }

 

- 검정할 그룹을 지정합니다.(PersonVO.java)

@JuminNo

private String jumin;    // 주민번호

 

@Size(min=8, max=20, groups=PersonGroupA.class)

private String password;  // 비밀번호

 

@NotEmpty

@Valid

List<RelationVO> relations;     // 가족

 

@NotNull

@Valid

private RelationVO recommender; // 추천인

 

- @JuminNo 아노테이션은 뒤에 만들게 될 커스텀 아노테이션 입니다.

- password에는 group=PersonGroupA.class 를 지정하여 Validator 에 PersonGroupA.class 를 검증하도록 표시되었을 때만 검증합니다.

- relations와 recommender는 @Valid 를 사용하여 내부의 멤버들도 검증하도록 지정합니다. 순환 참조일 경우 한번만 검증 됩니다.

 

 

 

 

3. 검증하기 (HomeController.java)

@Autowired

Validator validator;

 

...

 

@RequestMapping(value = "/personInsert.do", method = RequestMethod.POST)

public String personInsert(PersonVO personVO,  Errors errors, Model model) throws Exception {

 

    SpringValidatorAdapter validatorAdapter = new SpringValidatorAdapter(validator);

    validatorAdapter.validate(personVO, errors, Default.class, PersonGroupA.class);

 

    if(errors.hasErrors()) {

        return "personForm";

    }

    return "redirect:/personForm.do";

}

 

- SpringValidatorAdapter를 생성하여 validate 메소드로 검증합니다.

- validate 메소드의 세번째 인자는 가변 파라미터로 그룹을 지정하는 인터페이스를 여러개 지정할 수 있습니다.

- 그룹이 지정되지 않은 멤버의 검증은 Default.class 를 지정하여 수행합니다.

- 아래와 같이 @Validated 아노테이션을 사용하여 검증할 수도 있습니다.

 

@RequestMapping(value = "/personInsert.do", method = RequestMethod.POST)

public String personInsert(@Validated({Default.class,PersonGroupA.class}) PersonVO personVO,  Errors errors, Model model) throws Exception {

    if(errors.hasErrors()) {

        return "personForm";

    }

    return "redirect:/personForm.do";

}

 

- SpringValidatorAdapter를 사용할 경우 특정 입력값에 따라 검증 그룹을 동적으로 변경할 수 있을 것입니다.

 

 

4. 커스텀 검증 아노테이션 만들기

 

- @JuminNo 아노테이션을 만듭니다.(JuminNo.java)

- 아래 코드는 거의 대부분의 검증용 아노테이션에서 공통의로 사용되어 집니다. 인터페이스 이름만 바꿔 쓰면 되겠습니다.

- JuminNoValidator.class 가  실제 검증 로직을 가집니다.

 

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Constraint(validatedBy = {JuminNoValidator.class})

@ReportAsSingleViolation

public @interface JuminNo {

    String message() default "주민번호 형식이 틀립니다.";

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

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

 

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})

    @Retention(RetentionPolicy.RUNTIME)

    @Documented

    static @interface List {

        JuminNo[] value();

    }

}

 

- @Target : 아노테이션을 붙일수 있는 곳을 지정합니다.

- @Retention(RetentionPolicy.RUNTIME) : 아노테이션이 실행시에도 사용할 수 있도록 합니다.

- @Constraint : 검증용 클래스를 지정합니다.

- @ReportAsSingleViolation : 다른 아노테이션을 상속해서 기능을 만들때 여러개의 아노테이션을 상속 받아서 검증한다면 각 검증 메세지를 모두 가지고 있습니다. 이때 자신의 메세지만을 사용할 경우 필요합니다.(이 예제에서는 없어도 됩니다.)

 

- JuminNoValidator.java 만들기

 

public class JuminNoValidator implements ConstraintValidator<JuminNo, CharSequence> {

 

    @Override

    public boolean isValid(CharSequence value, ConstraintValidatorContext context) {

        if(value instanceof String) {

            return isJuminNo((String)value);

        }

        return isJuminNo(value.toString());

    }

 

    private boolean isJuminNo(String value) {

       ...

}

}

 

- isValid() 메소드가 true 를 반환하면 성공, false를 반환하면 실패입니다. 자세한 내용을 첨부된 소스를 참고 하세요.

 

 

5. 실행결과

 

 

 

 

※ 전체소스

Hibernate Bean Validator 사용하기(2) - 그룹과 커스텀 아노테이션

Hibernate Bean Validator 사용하기(1) - 기본 사용법

 

반응형