1. 회원가입 로직
1. 회원 역할(Role) 구분
- com.shop.constant.Role.java, enum 클래스 생성
- 일반 유저 / 관리자
<java />
package com.shop.constant;
public enum Role {
USER, ADMIN
}
2. 회원 가입 정보 DTO
- 회원 가입 화면으로 부터 넘어오는 가입정보를 담을 DTO 객체
<java />
@Getter
@Setter
public class MemberFormDto {
private String name;
private String emial;
private String password;
private String address;
}
3. Member Entity
- 회원 정보를 저장하는 Member Entity(Model) 생성
- Member 객체를 생성하기 위해 Member 객체 안에 createMember() static 메소드 생성
<java />
@Entity
@Table(name = "member")
@Getter @Setter
@ToString
public class Member {
@Id
@Column(name = "member_id")
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@Column(unique = true)
private String email;
private String password;
private String address;
@Enumerated(EnumType.STRING)
private Role role;
public static Member createMember(MemberFormDto memberFormDto, PasswordEncoder passwordEncoder) {
Member member = new Member();
member.setName(memberFormDto.getName());
member.setEmail(memberFormDto.getEmail());
member.setAddress(memberFormDto.getAddress());
String password = passwordEncoder.encode(memberFormDto.getPassword());
member.setPassword(password);
member.setRole(Role.USER);
return member;
}
}
4. MemberRepository
- 회원가입 시 중복 여부를 판단하기 위해서 이메일로 회원을 검사하도록 쿼리 메소드 작성
<java />
public interface MemberRepository extends JpaRepository<Member, Long> {
Member findByEmail(String email);
}
5. MemberService
- @RequiredArgsConstructor 를 사용하여 memberRepository DI
<java />
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
public Member saveMember(Member member) {
validateDuplicateMember(member);
return memberRepository.save(member);
}
public void validateDuplicateMember(Member member) {
Member findMember = memberRepository.findByEmail(member.getEmail());
if (findMember != null) {
throw new IllegalStateException("이미 가입된 회원입니다.);
}
}
}
6. 회원가입 기능 테스트
- 테스트 클래스 생성 및 DI
<java />
@SpringBootTest
@Transactional
@TestPropertySource(locations = "classpath:application-test.properties")
class MemberServiceTest {
@Autowired
MemberSerive memberService;
@Autowired
PasswordEncoder passwordEncoder;
// Controller에서 POST 입력 값을 기반으로 memberFormDto 객체를 생성할 예정
public Member createMember() {
MemberFormDto memberFormDto = new MemberFormDto;
memberFormDto.setEmail("test@email.com");
memberFormDto.setName("test");
memberFormDto.setAddress("서울시 중랑구 중화동")
memberFormDto.setPassword("1234")
return Member.createMember(memberFormDto, passwordEncoder);
}
- 회원가입 테스트
- 회원가입 DTO 객체를 Service 객체를 통해서 저장(save)
<java />
@Test
@DisplayName("회원가입 테스트")
public void saveMemberTest() {
Member member = creatMember();
Member saveMember = memberService.saveMember(member);
assertEquals(member.getEmail(), savedMember.getEmail());
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());
}
- 회원가입 중복 테스트
<java />
@Test
@DisplayName("중복 회원가입 테스트")
void validateDuplicateMember() {
// given
Member member1 = createMember();
Member member2 = createMember();
memberService.saveMember(member1);
// when
Throwable e = assertThrows(IllegalStateException.class, () -> {
memberService.saveMember(member2);
});
// then
assertEquals("이미 가입된 회원입니다.", e.getMessage());
}
- 테스트 결과
2. 회원가입 페이지
1. MemberController
- Get 요청으로 회원가입 페이지를 요청할 경우 MemberFormDto 객체를 같이 넘김
- POST 요청으로 넘어온 회원가입 정보를 MemberFormDto 객체로 받음
<java />
@RequestMapping("/members")
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
private final PasswordEncoder passwordEncoder;
@GetMapping(value = "/new")
public String memberForm(Model model) {
model.addAttribute("memberFormDto", new MemberFormDto());
return "member/memberForm";
}
@PostMapping(value = "/new")
public String memberForm(MeberFormDto memberFormDto) {
Member member = Member.createMember(memberFormDto, passwordEncoder);
memberService.saveMember(member);
return "redirect:/";
}
}
2. 회원 가입 페이지
- th:object - form submit 할 때, form 의 데이터가 th:object 객체에 매핑되어 보내짐
- th: field - 해당 필드를 th:object 객체 내부 필드와 매핑, "*{}" 형식으로 th:object 참조
- th:if="${#fields.hasErrors('필드명')}" - 해당 필드에 에러가 있는 경우
- th:errors - 해당 값에 에러가 있는 경우 출력
- csrf 토큰 전송
<code />
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/layout1}">
<!-- 사용자 CSS 추가-->
<th:block layout:fragment="css">
<style>
.fieldError {
color: #bd2130;
}
</style>
</th:block>
<!-- 사용자 스크립트 추가-->
<th:block layout:fragment="script">
<script th:inline="javascript">
$(document).ready(function(){
var errorMessage = [[${errorMessage}]];
if(errorMessage != null){
alert(errorMessage);
}
});
</script>
</th:block>
<div layout:fragment="content">
<form action="/members/new" role="form" method="post" th:object="${memberFormDto}">
<div class="form-group">
<label th:for="name">이름</label>
<input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력해주세요">
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="fieldError">Incorrect data</p>
</div>
<div class="form-group">
<label th:for="email">이메일주소</label>
<input type="email" th:field="*{email}" class="form-control" placeholder="이메일을 입력해주세요">
<p th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="fieldError">Incorrect data</p>
</div>
<div class="form-group">
<label th:for="password">비밀번호</label>
<input type="password" th:field="*{password}" class="form-control" placeholder="비밀번호 입력">
<p th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="fieldError">Incorrect data</p>
</div>
<div class="form-group">
<label th:for="address">주소</label>
<input type="text" th:field="*{address}" class="form-control" placeholder="주소를 입력해주세요">
<p th:if="${#fields.hasErrors('address')}" th:errors="*{address}" class="fieldError">Incorrect data</p>
</div>
<div style="text-align: center">
<button type="submit" class="btn btn-primary" style="">Submit</button>
</div>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
</form>
</div>
</html>
3. 회원 가입 페이지 화면

3. 회원가입 검증
1. validation 의존성 추가
- 서버로 넘어오는 값을 검증하기 위한 validation 라이브러리 추가
<code />
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. validation 어노테이션
어노테이션설명
@NotEmpty | NULL 체크 및 문자열의 경우 길이 0인지 검사 |
@NotBlank | NULL 체크 및 문자열의 경우 길이 0 및 빈 문자열(" ") 검사 |
@Length | 최소, 최대 길이 검사 |
이메일 형식인지 검사 | |
@Max | 지정한 값보다 작은지 검사 |
@Min | 지정한 값보다 큰지 검사 |
@Null | 값이 Null인지 검사 |
@NotNull | 값이 Null이 아닌지 검사 |
3. 회원가입 DTO 유효성 검증

<java />
@Getter @Setter
public class MemberFormDto {
@NotBlank(message = "이름은 필수 입력 값입니다.")
private String name;
@NotBlank(message = "이메일은 필수 입력 값입니다.")
@Email(message = "이메일 형식으로 입력해주세요.")
private String email;
@NotBlank(message = "비밀번호는 필수 입력 값입니다.")
@Length(min = 8, max = 16, message = "비밀번호는8자 이상, 16자 이하로 입력해주세요")
private String password;
@NotBlank(message = "주소는 필수 입력 값입니다.")
private String address;
}
4. 유효성 검증 결과에 따른 Controller
- 검증하려는 객체 앞에 @Value 어노테이션 지정
- 검증 결과를 받을 BindingResult 매개변수 추가
- 입력한 회원가입 데이터에 문제가 있으면 다시 회원가입 페이지로 돌아감
- 중복된 회원가입이면 지정된 예외가 발생하고 해당 에러 메시지를 다시 회원가입 페이지로 넘김
- 문제가 없으면 메인 페이지로 redirect
<java />
@PostMapping("/new")
public String newMember(@Valid JoinFormDto joinFormDto, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "member/memberForm";
}
try {
Member member = Member.createMember(memberFormDto, passwordEncoder);
memberService.saveMember(member);
} catch (Exception e) {
model.addAttribute("errorMessage", e.getMessage());
return "member/memberForm";
}
return "redirect:/";
}
5. 유효성 검증 결과
- 정상적인 경우 (비밀번호 암호화 적용됨)
- 입력한 데이터에 문제가 있는 경우

- 중복된 회원가입인 경우
'JAVA > SpringBoot Shoppingmall' 카테고리의 다른 글
[VSCODE] SpringBoot 쇼핑몰(MVN) 페이지 권한 설정 (0) | 2022.06.27 |
---|---|
[VSCODE] SpringBoot 쇼핑몰(MVN) 로그인/로그아웃 기능 구현 (0) | 2022.06.27 |
[VSCODE] SpringBoot 쇼핑몰(MVN) Sring Security (0) | 2022.06.27 |
[VSCODE] SpringBoot 쇼핑몰(MVN) Thymeleaf - 페이지 레이아웃 (0) | 2022.06.24 |
[VSCODE] SpringBoot 쇼핑몰(MVN) Thymeleaf (0) | 2022.06.24 |