회원가입과 로그인 시 발생할 수 있는 에러에는 무엇이 있을까?
대표적으로
- 중복 회원 가입
- 회원 가입 후 로그인 불일치
등이 있을 수 있겠다.
내가 공부하려 적는 포스팅이다.
다소 충분치 않은 코드들일 수 있다.
++ 에러를 처리하는 방법은 정말 많다.
정답이 없다.
(해도해도 끝이 없는 코딩의 세계...)
[MemberFormDto.java]
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
/**
* 회원 가입으로부터 넘어오는 가입정보를 담을 dto
* */
@Setter
@Getter
public class MemberFormDto {
@NotBlank(message = "이름은 필수 입력 값입니다.")
private String name;
@NotEmpty(message = "이메일은 필수 입력 값입니다.")
@Email(message = "이메일 형식으로 입력해주세요.")
private String email;
@NotEmpty(message = "비밀번호는 필수 입력 값입니다.")
@Length(min=8, max=16, message = "비밀번호는 8자 이상, 16자 이하로 입력해주세요")
private String password;
@NotEmpty(message = "주소는 필수 입력 값입니다.")
private String address;
}
→ Entity와 거의 유사한 형태임에도 Dto 클래스를 추가로 생성한다.
왜 그럴까?
Entity 클래스를 Request / Response 클래스로 사용하면 안 된다고 한다.
더 자세한 정보는 여기에 적어놓았다.
회원 가입 페이지에서 서버로 넘어오는 값을 검증하기 위해 validation을 추가한다.
Gradle 의존성(build.gradle)에는
implementation 'org.springframework.boot:spring-boot-starter-validation'
이것을 추가하고,
Maven 의존성(pom.xml)에는
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
을 추가한다.
아 갑자기 그래이들이랑 메이븐의 차이가 도대체 뭘까 궁금해지네.
어떤 것이 압도적으로 많다... 이런 건 모르겠고,
개발자들도 둘 다 쓰는 것 같다.
실제로 build.gradle이랑 pom.xml만 차이가 있지, 내부 코드에는 아~무런 차이가 없다.
성능 차이가 나나?
암튼 이건 다음에 알아보도록 하고.
자바 빈 밸리데이션을 이용하면 객체의 값을 효율적으로 검증할 수 있다고 한다.
Validation이 도대체 뭘까?
Validation
올바르지 않은 데이터를 걸러내고, 보안을 유지하기 위해 데이터 검증(validation)은
여러 계층에 걸쳐 적용된다.
스프링부트에서는 @validated를 이용해 유효성을 검증할 수 있다.
※ @Valid와 @Validated 차이
- @Valid : Java에서 지원해주는 어노테이션
- @Validated : Spring에서 지원해주는 어노테이션
@Validated는 @Valid의 기능을 포함하고,
유효성을 검토할 그룹을 지정할 수 있는 기능을 추가로 가지고 있다.
Bean Validation
스프링의 기본적인 validation인 Bean validation은 클래스 "필드"에 특정 annotation을
적용하여 필드가 갖는 제약 조건을 정의하는 구조로 이루어진 검사이다.
validator가 어떠한 비즈니스적 로직에 대한 검증이 아닌,
그 클래스로 생성된 객체 자체의 필드에 대한 유효성 여부를 검증한다.
javax.validation 어노테이션 예시
어노테이션 | 설명 |
@Null | null만 허용한다. |
@NotNull | 빈 문자열(""), 공백(" ")은 허용하되, Null은 허용하지 않음 |
@NotEmpty | 공백(" ")은 허용하되, Null과 빈 문자열("")은 허용하지 않음 |
@NotBlank | null, 빈 문자열(""), 공백(" ") 모두 허용하지 않는다. |
이메일 형식을 검사한다. 단, 빈 문자열("")의 경우엔 통과 시킨다. ( @Pattern을 통한 정규식 검사를 더 많이 사용 | |
@Pattern(regexp = ) | 정규식 검사할 때 사용한다. |
@Size(min=, max=) | 길이를 제한할 때 사용한다. |
@Max(value = ) | value 이하의 값만 허용한다. |
@Min(value = ) | value 이상의 값만 허용한다. |
@Positive | 값을 양수로 제한한다. |
@PositiveOrZero | 값을 양수와 0만 가능하도록 제한한다. |
@Negative | 값을 음수로 제한한다. |
@NegativeOrZero | 값을 음수와 0만 가능하도록 제한한다. |
@Future | Now 보다 미래의 날짜, 시간이어야 한다. |
@FutureOrPresent | Now 거나 미래의 날짜, 시간이어야 한다. |
@Past | Now 보다 과거의 날짜, 시간이어야 한다. |
@PastFutureOrPresent | Now 거나 과거의 날짜, 시간이어야 한다. |
출처 : https://dev-coco.tistory.com/123
(++ 여담인데, 이 분 블로그 보고 많이 공부한다.
구글링하면 상단에 노출 자주되는 블로그라 내적 친밀감 생김)
암튼
코드로 다시 돌아가서.
[MemberController.java]
@PostMapping("/new")
public String newMember(@Valid MemberFormDto memberFormDto, BindingResult bindingResult, Model model){ // 1, 2
if(bindingResult.hasErrors()){ // 3
return "member/memberForm";
}
try {
Member member = Member.createMember(memberFormDto, passwordEncoder);
memberService.saveMember(member);
} catch (IllegalStateException e){
model.addAttribute("errorMessage", e.getMessage()); // 4
return "member/memberForm";
}
return "redirect:/";
}
1. @Valid MemberFormDto memberFormDto, BindingResult bindingResult
: 검증하려는 객체의 앞에 @Valid 어노테이션을 선언하고,
파라미터로 bindingResult 객체를 추가한다.
2. 검사 후 결과는 bindingResult에 담아준다.
3. if(bindingResult.hasErrors()){...}
: bindingResult.hasErrors()를 호출하여 에러가 있다면,
회원 가입 페이지로 이동한다.
4. model.addAttribute("errorMessage", e.getMessage());
: 회원 가입 시 중복 회원 가입 예외가 발생하면,
에러 메시지를 뷰로 전달한다.
[MemberServiceTest.java]
public Member createMember() {
MemberFormDto memberFormDto = new MemberFormDto();
memberFormDto.setEmail("test@email.com");
memberFormDto.setName("김길동");
memberFormDto.setAddress("경기도");
memberFormDto.setPassword("1234");
return Member.createMember(memberFormDto, passwordEncoder);
}
회원 정보 만들어 주고,
@Test
@DisplayName("회원가입 테스트")
public void saveMemberTest(){
Member member = createMember();
Member savedMember = memberService.saveMember(member);
assertEquals(member.getEmail(), savedMember.getEmail());
assertEquals(member.getName(), savedMember.getName());
assertEquals(member.getAddress(), savedMember.getAddress());
assertEquals(member.getPassword(), savedMember.getPassword());
assertEquals(member.getRole(), savedMember.getRole());
}
회원 가입을 테스트 한다.
성공한다.
이젠 중복 회원이 있는지 검사해보자.
[MemberService.java]
public final MemberRepository memberRepository;
public Member saveMember(Member member) {
validateDuplicateMember(member);
return memberRepository.save(member);
}
private void validateDuplicateMember(Member member) {
Member findMember = memberRepository.findByEmail(member.getEmail());
if(findMember != null) {
throw new IllegalStateException("이미 가입된 회원입니다.");
}
}
→ validateDuplicateMember : MemberRepository에서 이메일(유니크키)로 멤버를 찾아온 뒤,
그 멤버가 있으면 IllegalStateException 예외를 발생시킨다.
(예외를 발생시키는 방법은 다양하다.
다른 것도 연습해볼 예정이다.
예를 들면, custom exception 같은 것들...)
또 테스트 코드를 작성해보자.
[MemberServiceTest.java]
@Test
@DisplayName("중복 회원 가입 테스트")
public void saveDuplicateMemberTest(){
Member member1 = createMember();
Member member2 = createMember();
memberService.saveMember(member1);
Throwable e = assertThrows(IllegalStateException.class, () -> {
memberService.saveMember(member2);});
assertEquals("이미 가입된 회원입니다.", e.getMessage());
}
→ member1과 member2를 위에 만들어 놓은 createMember로 설정해 놓고, 테스트한다.
assertThrows 메소드를 이용하면 예외 처리 테스트가 가능하다.
첫 번째 파라미터에는 발생할 예외 타입을 넣어준다.
위에서 IllegalStateException을 발생시키게 했으므로 맞춰서 한다.
또한 에러 메세지가 "이미 가입된 회원입니다"와 일치하는지 따져본다.
마찬가지로 통과한다.
'에러' 카테고리의 다른 글
[에러] 409 에러 (0) | 2023.01.18 |
---|---|
[에러] cannot construct instance of `...` & resolved [java.lang.nullpointerexception] (0) | 2023.01.17 |
[SpringBoot 에러] (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) (0) | 2023.01.08 |
git commit 삭제 (0) | 2023.01.04 |
Spring Boot와 MySQL 연동 방법 (0) | 2023.01.01 |