회원 가입 폼 서브밋 검증

Yuri Lee·2020년 10월 12일
0

JSR 303 애노테이션 검증

폼 값을 입력하고 전송 버튼을 누르면 폼 데이터들이 서버로 전송되는데, 사용자가 이때 유효하지 않는 값들을 입력하고 전송할 수도 있다. 이런 유효하지 못한 값들이 전송되었을 때 해당 데이터들을 검사해서 부적합하다고 판단되면 폼 입력 페이지로 다시 되돌리게 할 수 있다. 여기서 폼 데이터가 적합한지 체크하는 기능을 스프링이 제공해주는 Vaildator 인터페이스를 이용하여 구현할 수 있고, 또 다른 방법으로는 Hibernate에서 제공하는 JSR 303 Validation을 이용할 수 있다.

<form class="needs-validation col-sm-6" 
      action="#" th:action="@{/signup}" 
      th:object="${signUpForm}" method="post" novalidate>

method="post"에서 보이는 것과 같이 post를 사용해야 한다.

@PostMapping("/sign-up")
public String signUpSubmit(@Valid SignUpForm signUpForm, Error errors) {
        
}

@Valid 어노테이션을 사용해서 검증을 진행한다. 원래 객체로 바인딩을 받을 때ModelAttributes가 사용된다. 복합객체로 여러 값들(nickname, email, password)을 SignUpform 라는 객체 하나로 받아오는 것은 ModelAtributes 어노테이션을 사용해서 받아올 수 있다. 여기서 ModelAtributes 어노테이션은 파라미터로 사용시 생략할 수 있다. Errors를 할때 데이터를 바인딩할 때 발생하는 에러를 받아주는 것. 에러가 있으면 폼을 다시 보여주도록 만들었다.

여기서 valid 어노테이션을 사용하려고 했는데 오류가 발생했다 😤😤😤

Valid cannot be resolved to a type

해당 에러를 구글링 했고 (https://codedragon.tistory.com/6126) 여기서 답을 찾을 수 있었다. 이후 https://mvnrepository.com/artifact/org.hibernate/hibernate-validator/5.4.1.Final 에 들어가서 hibernate-validator dependency 를 추가해주었다. pom.xml 파일에 다음의 내용을 추가해주었다.

<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.4.1.Final</version>
</dependency>

근데 계속 오류가 나서 😨 댓글을 찾아보니까 스프링 부트가 2.3으로 올라가면서 validation 관련 의존성을 빼서 따로 분리했다고 한다. 스프링 부트가 관리하는 버전을 쓰려면 spring-boot-starter-validation을 의존성으로 추가해주시면 된다고 한다. 그래서 위 내용을 삭제하고 아래 내용을 추가해주었다.

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

이후 테스트 해보니 문제 없이 잘 돌아갔다!


패턴을 검증하기 위해 Pattern 어노테이션을 활용해 정규식을 넣어주었다.

SignUpForm.java

@NotBlank
@Length(min = 3, max = 20)
@Pattern(regexp = "^[ㄱ-ㅎ가-힣a-z0-9_-]{3,20}$")

SignUpForm.java 에 정규식으로 설정해놓았던 패턴에 어긋났기 때문에 다음과 같은 메시지가 출력된다. (즉 서버를 갔다 온 것! 백엔드에서 패턴에 어긋났기 때문에 ..)

서버에 오기 전에 프론트에서 걸러주면 리소스를 절약할 수 있고 사용자에게 더 빠르게 fail-fast 를 할 수 있다. 뒤늦게 가서 실패하는 게 아니라 ! 공항까지 가서 여권을 안가져온게 아니라 출발할 때 여권을 안챙겼네 하는 느낌...

⭐ 프론트에도 검증을 해야 하고, 백엔드에서도 검증을 해야 한다.

커스텀 검증

  • 중복 이메일
  • 닉네임 여부 확인

SignUpFormValidator.java 을 만들어준다. 이 SignUpFormValidator는 SignUpForm type 타입의 인스턴스를 검증할 것이다. 그럼 무엇을 검사? 실제 db에서 조회해야 한다. 여기다가 Validator라는 스프링 인터페이스를 구현할 것! 추가적으로 db에서 실제 email, nickname이 중복되는지 아닌지 검사를 하려면 AccountRepository.java가 있어야 한다. 아직 생성을 안했으니 만들어주고 JpaRepository를 extends 해준다.

@Component
//@RequiredArgsConstructor
public class SignUpFormValidator implements Validator {

    private final AccountRepository accountRepository;

    public SignUpFormValidator(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

어떤 빈이 생성자가 하나만 있고, 그 생성자가 받는 파라미터들이 빈으로 등록되어있다면 스프링 4.22 이후부터는 자동으로 빈을 주입해주기 때문에 autowired 나 injection 없이도 빈으로 주입을 받을 수 있다.

@Controller
@RequiredArgsConstructor
public class AccountController {

    private final SignUpFormValidator signUpFormValidator;

    @GetMapping("/sign-up")
    public String signUpForm(Model model) {
        model.addAttribute(new SignUpForm());
        return "account/sign-up";
    }

    @PostMapping("/sign-up")
    public String signUpSubmit(@Valid SignUpForm signUpForm, Errors errors) {
        if (errors.hasErrors()) {
            return "account/sign-up";
        }

        signUpFormValidator.validate(signUpForm, errors);
        if (errors.hasErrors()) {
            return "account/sign-up";
        }
        
        //to do 회원가입 처리
        return "redirect:/";
    }
}

줄일 수 있는 방법이 있을까? 줄이기보다는 적절한 방법으로 빼내는 방법이 있다. InitBinder 사용하기, InitBinder로 signUpform이라는 데이터를 받을 때 바인더를 설정할 수 있다. 웹 데이터 바인더를 파라미터를 받아서, Validator 를 추가할 수 있다. 우리가 만든(ex. SignUpFormValidator) SignUpForm을 받을 때, 이 타입을 따라간다.

    @InitBinder("signUpForm")
    public void initBinder(WebDataBinder webDataBinder) {
        webDataBinder.addValidators(signUpFormValidator);
    }

@InitBinder signUpForm이라는 데이터를 받을 때 바인더를 설정할 수 있다. 바인딩을 할 수 있는.. 웹 데이터 바인더에다가 validator를 추가할 수 있다.

번외

이 어노테이션은 초기화 되지않은 final 필드나, @NonNull 이 붙은 필드에 대해 생성자를 생성해 줍니다. 주로 의존성 주입(Dependency Injection) 편의성을 위해서 사용되곤 한다. autowired 없이도 롬복이 만들어줌. 어떤 Bean이 생성자가 하나만 있고, 생성자가 받는 파라미터들이 빈으로 등록되어있으면 스프링 4.2 부터 자동으로 빈을 주입해주기 때문에 autowired, injection 없이 빈으로 주입을 받을 수 있다.

@Component
개발자가 직접 작성한 Class를 Bean으로 등록하기 위한 어노테이션

@RequiredArgsConstructor

private final type의 member variable을 생성자들을 만들어준다. (private String name 같은것은 만들어주지 않음) 선별적으로 우리가 반드시 만들어질 생성자를 고를 수 있기 때문에 .. lombok이 생성자들이 어노테이션을 통해 만들어줌.

스프링 IoC(Inversion of Control) 컨테이너에 의해서 관리되고 애플리케이션의 핵심을 이루는 객체들을 스프링에서는 빈즈(Beans)라고 부른다. 빈은 스프링 Ioc 컨테이너에 의해서 인스턴스화되어 조립되거나 관리되는 객체를 말한다. 이같은 점을 제외하고 빈은 수많은 객체들중의 하나일 뿐이다. 빈과 빈 사이의 의존성은 컨테이너가 사용하는 메타데이터 환경설정에 반영된다.

SI를 할때, 현재 어느 쇼핑몰 운영을 맡으며 개발 소스를 보면 많은 비즈니스 로직이 Controller에 절차지향적으로 짜여져 있는걸 보게 된다. 이런걸 볼때마다 조금씩 business을 하나의 트랜잭션 단위로, Service쪽으로 옮기는 작업을 할 수 있다.이로 인해 그나마 객체지향적으로 코딩할 수 있고, 재사용 할 수 있음과 동시에 Transaction 단위로도 개발하기 편해진다.

  1. 트랜잭션의 성질
  • 원자성(Atomicity) : 한 트랜잭션 내에서 실행한 작업들은 하나로 간주한다. 즉, 모두 성공 또는 모두 실패.
  • 일관성(Consistency) : 트랜잭션은 일관성 있는 데이타베이스 상태를 유지한다. (data integrity 만족 등.)
  • 격리성(Isolation) : 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않도록 격리해야한다.
  • 지속성(Durability) : 트랜잭션을 성공적으로 마치면 결과가 항상 저장되어야 한다.
  1. 스프링에서 트랜잭션 처리 방법

스프링에서는 트랜잭션 처리를 지원하는데 그중 어노테이션 방식으로 @Transactional을 선언하여 사용하는 방법이 일반적이며, 선언적 트랜잭션이라 부른다. 클래스, 메서드위에 @Transactional 이 추가되면, 이 클래스에 트랜잭션 기능이 적용된 프록시 객체가 생성된다. 이 프록시 객체는 @Transactional이 포함된 메소드가 호출 될 경우, PlatformTransactionManager를 사용하여 트랜잭션을 시작하고, 정상 여부에 따라 Commit 또는 Rollback 한다.


출처 : 인프런 백기선님의 스프링과 JPA 기반 웹 애플리케이션 개발
http://blog.naver.com/PostView.nhn?blogId=musicovery12&logNo=220599165889
https://galid1.tistory.com/494
https://medium.com/webeveloper/requiredargsconstructor-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-dependency-injection-4f1b0ac33561
https://endorphin0710.tistory.com/93
https://goddaehee.tistory.com/167 [갓대희의 작은공간]

profile
Step by step goes a long way ✨

0개의 댓글