해당 코드는 Github를 확인해주세요.
스프링은 컨트롤러에서 클라이언트에서 넘겨받은 값에 대한 검증을 JSR-303 기반으로 쉽고 강력하게 할 수 있습니다. 또 한 커스텀 한 어노테이션을 확장도 쉽게 구현할 수 있습니다.
아래에서 작성하는 어노테이션은 해당 이메일이 유니크한지 검증을 하고 유니크하지 않은 이메일일 경우 Bad Request를 응답하는 어노테이션 입니다.
어노테이션 정의
1 2 3 4 5 6 7 8 9 10 11 12
   | @Documented @Constraint(validatedBy = EmailDuplicationValidator.class) @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface EmailUnique {
    String message() default "Email is Duplication";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {}; }
   | 
 
Validator 로직 작성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | @Component @RequiredArgsConstructor public class EmailDuplicationValidator implements ConstraintValidator<EmailUnique, String> {
    private final MemberRepository memberRepository;
    @Override   public void initialize(EmailUnique emailUnique) {
    }
    @Override   public boolean isValid(String email, ConstraintValidatorContext cxt) {
      boolean isExistEmail = memberRepository.existsByEmail(email);
      if (isExistEmail) {       cxt.disableDefaultConstraintViolation();       cxt.buildConstraintViolationWithTemplate(           MessageFormat.format("Email {0} already exists!", email))           .addConstraintViolation();     }     return !isExistEmail;   } }
   | 
 
넘겨 받은 email이 존재하는지 조회하고 중복되느 값이면 예외 메시지를 추가하고 isValid(...) 메서드에서 false를 리턴합니다. 
Test
API Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | public class SignUpRequest {     @EmailUnique @Email     private String email; }
  public class MemberApi {
    private final MemberRepository memberRepository;
    @PostMapping   public Member create(@RequestBody @Valid final SignUpRequest dto) {
      return memberRepository.save(Member.builder()         .email(dto.getEmail())         .build());   }
  }
  | 
 
Test Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | public class MemberApiTest {
    @Test   public void signUp_test_이메일이_중복된_경우() throws Exception {          final SignUpRequest dto = new SignUpRequest("yun@test.com");
           final ResultActions resultActions = requestSignUp(dto);
           resultActions         .andExpect(status().isBadRequest());   }
    private ResultActions requestSignUp(SignUpRequest dto) throws Exception {     return mockMvc.perform(post("/members")         .contentType(MediaType.APPLICATION_JSON_UTF8)         .content(objectMapper.writeValueAsString(dto)))         .andDo(print());   }
  }
  | 
 
Response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
   | {     "timestamp": "2019-03-19T17:11:26.919+0000",     "status": 400,     "error": "Bad Request",     "errors": [         {             "codes": [                 "EmailUnique.signUpRequest.email",                 "EmailUnique.email",                 "EmailUnique.java.lang.String",                 "EmailUnique"             ],             "arguments": [                 {                     "codes": [                         "signUpRequest.email",                         "email"                     ],                     "arguments": null,                     "defaultMessage": "email",                     "code": "email"                 }             ],             "defaultMessage": "Email yun@test.com already exists!",             "objectName": "signUpRequest",             "field": "email",             "rejectedValue": "yun@test.com",             "bindingFailure": false,             "code": "EmailUnique"         }     ],     "message": "Validation failed for object='signUpRequest'. Error count: 1",     "path": "/members" }
  | 
 
테스트 코드를 실행해보면 EmailDuplicationValidator 로직이 정상 동작하는지 확인할 수 있습니다.