스프링과 JPA 기반 웹 애플리케이션 개발 #10 회원 가입 폼 서브밋 검증

Jake Seo·2021년 5월 26일
0

스프링과 JPA 기반 웹 애플리케이션 개발 #10 회원 가입 폼 서브밋 검증

해당 내용은 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발의 강의 내용을 바탕으로 작성된 내용입니다.

강의를 학습하며 요약한 내용을 출처를 표기하고 블로깅 또는 문서로 공개하는 것을 허용합니다 라는 원칙 하에 요약 내용을 공개합니다. 출처는 위에 언급되어있듯, 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발입니다.

제가 학습한 소스코드는 https://github.com/n00nietzsche/jakestudy_webapp 에 지속적으로 업로드 됩니다. 매 커밋 메세지에 강의의 어디 부분까지 진행됐는지 기록해놓겠습니다.


회원 가입: 폼 서브밋

회원 가입 폼 검증

  • JSR 303 애노테이션 검증
    • 값의 길이, 필수값
  • 커스텀 검증
    • 중복 이메일, 닉네임 여부 확인
  • 폼 에러 있을 시, 폼 다시 보여주기

회원 가입 처리

  • 회원 정보 저장
  • 인증 이메일 발송
  • 처리 후 첫 페이지로 리다이렉트 (PRG(Post-Redirect-Get) 패턴)

JSR 303 애노테이션 검증 적용하기

JSR 303 애노테이션 검증이란, 공식 문서의 설명을 그대로 번역하면

This document is the specification of the Java API for JavaBean validation in Java EE and Java SE. The technical objective of this work is to provide a class level constraint declaration and validation facility for the Java application developer, as well as a constraint metadata repository and query API.

이 문서는 Java EE(Enterprise Edition)과 Java SE(Standard Edition)의 자바빈 검증을 위한 자바 API의 설명서입니다. 이 작업의 기술적인 목적은 자바 앱 개발자들에게 클래스 레벨 제약 선언 및 검증 기능과 제약 메타데이터 리포지토리 그리고 쿼리 API를 제공하는 것입니다.

Java SE란?

When most people think of the Java programming language, they think of the Java SE API. Java SE's API provides the core functionality of the Java programming language. It defines everything from the basic types and objects of the Java programming language to high-level classes that are used for networking, security, database access, graphical user interface (GUI) development, and XML parsing.

In addition to the core API, the Java SE platform consists of a virtual machine, development tools, deployment technologies, and other class libraries and toolkits commonly used in Java technology applications.

Java EE란?

The Java EE platform is built on top of the Java SE platform. The Java EE platform provides an API and runtime environment for developing and running large-scale, multi-tiered, scalable, reliable, and secure network applications.

아무튼 짧게 말하면 javax.validation 내부에 있는 애노테이션 API로 잡다한 검증 체계를 만드는 것이다.

검증을 위한 의존성 추가

스프링 부트 2.3버전 이후부터 validationSpringboot web의존성에서 빠져서 직접 넣어주어야 한다.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

SignUpForm 클래스에 제약조건 설정

@Data
public class SignUpForm {
    @NotBlank
    @Length(min = 3, max = 20)
    @Pattern(regexp = "^[ㄱ-ㅎ가-힣a-z0-9_-]{3,20}$")
    private String nickname;

    @Email
    @NotBlank
    private String email;

    @NotBlank
    @Length(min = 8, max = 50)
    private String password;
}

위와 같이 @NotBlank(비어있으면 안됨), @Length(길이 제약), @Pattern(정규표현식으로 형식 제약), @Email(Email 형태로 제약)

AccountController 클래스에 제약조건 검증 설정

@PostMapping("/sign-up")
public String signUpSubmit(@Valid @ModelAttribute SignUpForm signUpForm, Errors errors) {
    // ERRORS 객체는 특정한 객체에 대해 데이터 바인딩과 검증에 대한 정보를 저장하고 노출시킨다.
    // 필드 네임으로 타겟 오브젝트의 프로퍼티들 혹은 하위 오브젝트의 경우 중첩된 필드들을 사용할 수 있다.
    // `setNestedPath(String)`을 통해 서브 트리 네비게이션을 지원한다.
    // 주의 에러 오브젝트는 single-threaded 되어 있다.
    if(errors.hasErrors()) {
    	return "account/sign-up";
    }

	// TODO: 회원가입 처리
	return "redirect:/";
}

검증을 위해 SignUpForm 클래스 앞에 @Valid 애노테이션을 붙여주었다. 또한 파라미터로 Errors 객체를 받아서, 해당 검증에 에러가 있다면, .hasErrors()true를 반환하여, submit 이 처리되지 않고, 다시 한번 입력 폼을 띄워준다.

커스텀 검증

AccountRepository 작성하기

// JPA Data 이용할 때는 항상 인터페이스로 만들어야 하는 것 유의하자.
// Transactional 애노테이션은 항상 스프링 프레임워크에 있는거 쓰자. (기능이 더 많음)
// readOnly 를 통해 write 에 쓰이는 lock 에 대한 연산을 없애서 성능을 약간이라도 최적화할 수 있다.
@Transactional(readOnly = true)
public interface AccountRepository extends JpaRepository<Account, Long> {
    boolean existsByEmail(String email);
    boolean existsByNickname(String nickname);
}

Spring Data JPA를 이용하여 작성하였다. 약간의 성능 최적화를 위해서 @Transactional(readOnly = true) 적용해주었다. 이러면 write에 대한 lock이 걸리지 않아서 조금 더 빠르다고 한다.

SignUpFormValidator 작성하기

// 인터페이스를 상속할 때, 스프링의 Validator 를 쓰도록 유의하자.
// Bean 이 되어 컴포넌트 스캔에 걸리도록 만들어야 의존성을 주입받을 수 있다.
@Component
// 리포지토리를 생성자 주입받기 위해서 작성해준 lombok 애노테이션
@RequiredArgsConstructor
public class SignUpFormValidator implements Validator {

    private final AccountRepository accountRepository;

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

    @Override
    public void validate(Object o, Errors errors) {
        // TODO: email, nickname 중복 여부
        SignUpForm signUpForm = (SignUpForm) errors;

        if(accountRepository.existsByEmail(signUpForm.getEmail())) {
            // rejectValue() 메소드는 주어진 에러 상세설명을 사용하여 현재 오브젝트의 정의된 필드에 대한 필드 에러를 등록한다.
            // 필드 이름은 오브젝트의 필드보다는 오히려 현재 오브젝트 그 자체를 가리키기 위해서 `null` 혹은 빈 문자열일 수 있다.
            // 만일, 현재 오브젝트가 최상위 오브젝트라면, 중첩된 오브젝트 그래프 혹은 글로벌 에러 안에서 서로 대응하는 필드 에러를 초래할 수 있다.
            // 파라미터 설명
            // field - 필드 이름 (null 혹은 빈 문자열 가능)
            // errorCode - 에러 코드, 메세지 키로 번역 가능
            // errorArgs - 에러 아규먼트들, 메세지 포멧을 통한 아규먼트 바인딩 (null 가능)
            // defaultMessage - 일단 나타나는 기본 메세지
            errors.rejectValue("email", "invalid.email", new Object[]{signUpForm.getEmail()}, "이미 사용중인 이메일입니다.");
        }

        if(accountRepository.existsByNickname(signUpForm.getNickname())){
            errors.rejectValue("nickname", "invalid.nickname", new Object[]{signUpForm.getNickname()}, "이미 사용중인 닉네임입니다.");
        }
    }
}
  • 인터페이스를 상속할 때, 스프링의 Validator 를 쓰도록 유의하자. (javax.validator도 있어서 헷갈린다)
  • Bean 이 되어 컴포넌트 스캔에 걸리도록 만들어야 의존성을 주입받을 수 있다.
    • Bean으로 만든 이유는 또 다른 Bean인 AccountRepository를 주입받기 위해서이다.
  • Spring Data JPA의 인터페이스를 활용하여 existsBy... 메소드를 자동 생성했다.
  • errors.rejectValue() 메소드는 추후 사용자에게 어떤 에러가 발생했는지에 대한 알람을 주는 데에 도움이 될 것이다.

AccountController 클래스 수정하기

public class AccountController {

    private final SignUpFormValidator signUpFormValidator;

    @InitBinder("signUpForm")
    public void initBinder(WebDataBinder webDataBinder) {
        // SignUpForm 객체를 받을 때 자동으로 검증이 들어감
        webDataBinder.addValidators(signUpFormValidator);
    }
    ...

위와 같이 @initBinder 애노테이션을 통해, SignUpFormValidator를 의존성 주입받고, 생성한 Validator가 자동으로 적용되게 할 수 있다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글