JPA 회원가입 구현하기
들어가며
이번에는 JpaRepository<>를 이용하요 쉽게 사용할수있지만 사용하지 못하는 분들도 있을수 있어서 직접 메소드를 생성하여 DB에서 데이터를 저장하고 수정하고 조회를 할수 있게끔 작성하려고한다. 나중에는 JpaRepository<>를 사용할예정이다. 그리고.. .CSS를 잘 다루지 못해서... 회원가입 페이지는 꾸미지 못했습니다..ㅜ
예상 화면
- 아이디 중복검사 버튼
- 비밀번호 중복검사
- 메일 인증을 통해 본인 인증
- 카카오 주소찾기 API를 이용한 우편번호 찾기
등을 사용하여서 회원가입 페이지를 작성할 예정입니다!
앤티티 구성
회원가입을 하기위해서는 일단 Member앤티티를 구성할것이다 아이디를 구별할수있는 id를 만들고 그아래는 회원가입시 필요한 인적정보등을 나타낸다 role은 enum타입으로 구성할 예정이며, 권한별로 들어갈수있는 홈페이지를 따로 보여주기 위위함이며, Adress 에는 우편번호 상세주소 서브주소등을 나타내기 위해서 별도로 클래스를 만들었다.
Member 앤티티 작성
entity/member 폴더를 생성후에 Member.class를 작성
Member
@Entity
@Getter @Setter
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String loginId;
private String username;
private String password1;
private String password2;
private String email;
private String brithDay;
@Enumerated(EnumType.STRING)
private Role role;
@Embedded
private Address address;
@Builder
public Member(String loginId, String username, String password1, String password2, String email, String brithDay, Role role, Address address) {
this.loginId = loginId;
this.username = username;
this.password1 = password1;
this.password2 = password2;
this.email = email;
this.brithDay = brithDay;
this.role = role;
this.address = address;
}
}
- @Embedded : 또 다른 값 객체를 표현할때 사용하는 어노테이션으로 Address를 따로 분류하여서 사용해줬다.
- @Enumerated : Enum타입을 명시할떄 사용하는 어노테이션으로 문자로 사용할예정이라 EnumType.String
Adress
@Data
@Getter@Setter
@Embeddable
@NoArgsConstructor
public class Address {
@NotBlank(message = "주소를 입력해주세요")
private String zipcode;
private String address;
private String detailAddr;
private String subAddr;
@Builder
public Address(String zipcode, String address, String detailAddr, String subAddr) {
this.zipcode = zipcode;
this.address = address;
this.detailAddr = detailAddr;
this.subAddr = subAddr;
}
}
Role
public enum Role {
admin, user
}
MemberRepository
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
public void save(Member member){
em.persist(member);
em.flush();
}
객체지향적으로 인터페이스로 구성하여 상속받아서 구현할수도있지만 매우 간단하다고 생각하여 이렇게 작성했다.
- save(Member member) : 멤벌 객체를 저장하기 위한 메소드
MemberService
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
public void join(MemberDto dto){
Member entity = dto.toEntity(dto);
memberRepository.save(entity);
}
}
- join(MemberDto dto) : 직접 Member객체에 접근해서 사용하지 않고 Dto를 생성하여 MemberRepository로 위임하는 역활을한다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
검증 기능을 사용하기 위해서 Build.gradle 에다가 위에 코드를 추가한다.
MemberDto
@Data
@Getter@Setter
@NoArgsConstructor
public class MemberDto {
private Long id;
@NotBlank(message = "아이디를 입력하세요")
private String loginId;
@NotBlank(message = "이름을 입력하세요")
private String username;
@NotBlank(message = "비밀번호를 입력하세요")
private String password1;
@NotBlank(message ="비밀번호를 다시한번 입력해주세요" )
private String password2;
@NotBlank(message = "이메일을 입력해주세요")
private String email;
@NotBlank(message = "생년월일 8자리를 입력해주세요")
private String brithDay;
private Role role;
private Address address;
@Builder
public MemberDto( String loginId, String username, String password1, String password2, String email, String brithDay, Role role, Address address) {
this.loginId = loginId;
this.username = username;
this.password1 = password1;
this.password2 = password2;
this.email = email;
this.brithDay = brithDay;
this.role = role;
this.address = address;
}
public Member toEntity(MemberDto memberDto){
Member entity = Member.builder()
.loginId(memberDto.loginId)
.username(memberDto.username)
.password1(memberDto.password1)
.password2(memberDto.password2)
.email(memberDto.email)
.brithDay(memberDto.brithDay)
.role(memberDto.role)
.address(memberDto.address)
.build();
return entity;
}
public MemberDto of(Member member){
MemberDto dto = MemberDto.builder()
.loginId(member.getLoginId())
.username(member.getUsername())
.password1(member.getPassword1())
.password2(member.getPassword2())
.email(member.getEmail())
.brithDay(member.getBrithDay())
.role(member.getRole())
.address(member.getAddress())
.build();
return dto;
}
}
- toEntity(MemberDto memberDto) : 실제 entity객체를 가저오는 메소드
- of(Member member) : 실제 객체를 dto로 변환하는 메소드
실제 객체인 Member을 사용하게 되면 변경시 번거로운 일이 발생하여 따로 Dto라는 클래스를 만들어 Dto를 사용해 유지보수성을 챙기고 성능을 향상시키기 위해서 사용한다.
MemberController
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping("/join")
public String joinGet(@ModelAttribute("member") MemberDto memberDto){
return "members/join";
}
@PostMapping("/join")
public String joinPost(@Valid MemberDto memberDto, BindingResult bindingResult){
if(bindingResult.hasErrors()){
return "members/join";
}
memberService.join(memberDto);
return "members/complete";
}
}
회원가입 성공시 회원가입이 완료 됬다는 페이지로 이동하게 한다.
html작성
join.html
<html xmlns:th="http://www.thymeleaf.org">
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
.invalid-feedback{
color: #dc3545;
font-weight: 600;
}
</style>
<body>
<div class="container mt-5">
<h1 class="text-center">회원가입 페이지</h1>
<form role="form" action="/join" th:object="${member}" method="post">
<div class="form-group">
<label th:for="loginId" th:class="mb-0">아이디</label>
<input type="text" th:field="*{loginId}" class="form-control form-control-sm" placeholder="아이디를 입력하세요"
th:class="${#fields.hasErrors('loginId')} ? 'form-control field-error' : 'form-group'">
<button type="button" onclick="checkDuplicateId()">아이디 중복 검사</button>
<div class="invalid-feedback" th:if="${#fields.hasErrors('loginId')}">
<small th:class="text-danger" th:errors="*{loginId}"></small>
</div>
<div id="duplicateMessage"></div>
</div>
<div class="form-group">
<label th:for="username">이름</label>
<input type="text" th:field="*{username}" class="form-control" placeholder="이름을 입력하세요"
th:class="${#fields.hasErrors('username')}? 'form-control field-error' : 'form-group'">
<div class="invalid-feedback" th:if="${#fields.hasErrors('username')}">
<small th:class="text-danger" th:errors="*{username}"></small>
</div>
</div>
<div class="form-group">
<label th:for="password1">비밀번호</label>
<input type="password" th:field="*{password1}" class="form-control" placeholder="비밀번호를 입력하세요"
th:class="${#fields.hasErrors('password1')}? 'form-control field-error' : 'form-group'">
<div class="invalid-feedback" th:if="${#fields.hasErrors('password1')}">
<small th:class="text-danger" th:errors="*{password1}"></small>
</div>
</div>
<div class="form-group">
<label th:for="password2">비밀번호 재입력</label>
<input type="password" th:field="*{password2}" class="form-control" placeholder="비밀번호를 다시한번 입력해주세요"
th:class="${#fields.hasErrors('password2')}? 'form-control field-error' : 'form-group'">
<button type="button" th:onclick="checkpwd()">중복검사</button>
</div>
<div class="form-group">
<label th:for="brithDay">생년월일</label>
<input type="text" th:field="*{brithDay}" class="form-control" placeholder="생년원일 8자리"
th:class="${#fields.hasErrors('brithDay')}? 'form-control field-error' : 'form-group'">
<div class="invalid-feedback" th:if="${#fields.hasErrors('brithDay')}">
<small th:class="text-danger" th:errors="*{brithDay}"></small>
</div>
</div>
<div class="form-group list email_input">
<label th:for="email" id="mailTxt">이메일 입력해주세요</label>
<input type="text" th:field="*{email}" class="form-control"
th:class="${#fields.hasErrors('email')}? 'form-control field-error : form-group list email_input'">
<button class="btn btn-outline-primary" type="button" id="checkEmail">인증번호</button>
<div class="invalid-feedback" th:if="${#fields.hasErrors('email')}">
<small th:class="text-danger" th:errors="*{email}"></small>
</div>
</div>
<div class="form group email_input">
<label for="memailconfirm" id="memailconfirmTxt">인증번호를 입력해 주세요</label>
<input type="text" class="form-control" id="memailconfirm">
</div>
<div class="from-group">
<label th:for="address">주소</label>
<input type="text" th:field="*{address.zipcode}" placeholder="우편번호">
<input type="button" onclick="sample6_execDaumPostcode()" value="우편번호 찾기"
th:class="${#fields.hasErrors('address')}? 'form-control field-error' : 'form-group'"><br>
<div class="invalid-feedback" th:if="${#fields.hasErrors('address')}">
<small th:class="text-danger" th:errors="*{address}"></small>
</div>
<input type="text" th:field="*{address.address}" placeholder="주소"><br>
<input type="text"th:field="*{address.detailAddr}" placeholder="상세주소">
<input type="text" th:field="*{address.subAddr}" placeholder="참고항목">
</div>
<button class="btn" type="submit">회원가입</button>
</form>
</div>
</body>
</html>
아이디 중복검사, 비밀번호 중복검사, 인정번호전송,우편번호찾기는 구현하지 않았다. 하나씩 구현할 예정입니다~!
complete.html
</head>
<body>
<div>
<h2>회원가입 완료</h2>
</div>
<br>
<div>
<h3>로그인하러가기</h3>
<a class="btn btn-lg btn-primary" href="/login">로그인</a>
<a class="btn btn-lg btn-secondary" href="/">메인페이지</a>
</div>
</body>
</html>
그리고 header.html에서 회원가입 부분에 href="" 를 herf="/join"으로 변경한다.
실행
다음과 같이 뜨면 성공
그후에 회원가입 버튼을 누르게 되면 아무 값도 입력하지 않으면 오류메시지를 작게 글씨로 나타나고 값을 모두 입력하고 회원가입을 누르면 회원가입 완료되며 완료 페이지로 이동하게 했다.
그 후에 DB에 올바르게 저장되어 있는 것을 볼 수 있다 role은 따로 권한을 부여하는 것이어서 회원가입을 할 때는 적용 x
다음으로
다음에는 아이디 중복검사, 비민번호중복검사를 구현해 보도록 하겠습니다~ 감사합니다!
'ToyProject쇼핑몰' 카테고리의 다른 글
[스프링 부트] JPA 쇼핑몰 # 6.회원가입 주소 (0) | 2023.10.27 |
---|---|
[스프링 부트] JPA 쇼핑몰 # 5.메일 인증 (0) | 2023.10.27 |
[스프링 부트] JPA 쇼핑몰 프로젝트 # 4.중복검사(중복체크) (0) | 2023.10.26 |
[스프링 부트] JPA 쇼핑몰 프로젝트 # 2.메인페이지 구성 (0) | 2023.10.20 |
[스프링 부트] JPA 쇼핑몰 프로젝트 #1.프로젝트 준비 (0) | 2023.10.20 |