회원 정보 수정을 구현 하도록 하자.
[Member Entity]
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Member extends BaseEntity{
/**
* member_id(PK)
* username
* password
* email
* phone
* userRole
*/
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private String password;
private String email;
private String phone;
@Enumerated(EnumType.STRING)
private UserRole userRole = UserRole.USER;
@OneToMany(mappedBy = "member")
private List<Task> tasks = new ArrayList<>();
}
회원 엔티티는 위와 같이 구성되어 있다.
수정 가능한 회원 정보는
1. password
2. email
3. phone
이다.
{'password'}
{'email', 'phone'}
이렇게 2그룹으로 나누어 수정을 구현하겠다.
이번 글에선 {'email', 'phone'} 을 수정한다.
[MemberRequestDto.class]
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberRequestDto {
public interface Create {}
public interface UpdateEmailPhone {} //Email, Phone 수정 그룹
public interface UpdatePassword {}
public interface IdOnly {}
@NotNull(groups = {UpdateEmailPhone.class, UpdatePassword.class, IdOnly.class}, message = "ID는 필수 입력 값입니다.")
private Long id;
@Pattern(regexp = "^[a-zA-Z0-9]{4,20}$", groups = Create.class,
message = "아이디는 영문 대소문자, 숫자로 이루어진 4~20자리여야 합니다.")
@NotBlank(groups = Create.class, message = "아이디는 필수 입력 값입니다.")
private String username;
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}", groups = {Create.class, UpdatePassword.class},
message = "비밀번호는 8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요.")
@NotBlank(groups = {Create.class, UpdatePassword.class}, message = "비밀번호는 필수 입력 값입니다.")
private String password;
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}", groups = {Create.class, UpdatePassword.class},
message = "비밀번호는 8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요.")
@NotBlank(groups = {Create.class, UpdatePassword.class}, message = "비밀번호는 필수 입력 값입니다.")
private String passwordCheck;
@Pattern(regexp = "^(?:\\w+\\.?)*\\w+@(?:\\w+\\.)+\\w+$", groups = {Create.class, UpdateEmailPhone.class},
message = "이메일 형식이 올바르지 않습니다.")
@NotBlank(groups = {Create.class, UpdateEmailPhone.class}, message = "이메일은 필수 입력 값입니다.")
private String email;
@Pattern(regexp = "^01(?:0|1|[6-9])-(?:\\d{3}|\\d{4})-\\d{4}$", groups = {Create.class, UpdateEmailPhone.class},
message = "전화번호 형식이 올바르지 않습니다.")
@NotBlank(groups = {Create.class, UpdateEmailPhone.class}, message = "전화번호는 필수 입력 값입니다.")
private String phone;
// update Form
/**
* 수정 가능 필드
* @param email
* @param phone
*/
public MemberRequestDto(Long id, String email, String phone) {
this.id = id;
this.email = email;
this.phone = phone;
}
}
{'email', 'phone'} 가 'UpdateEmailPhone'으로 한 그룹으로 묶인것을 확인할 수 있다.
[MemberResponseDto.class]
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberResponseDto {
private Long id;
private String username;
private String password;
private String email;
private String phone;
private String createdAt;
private String updatedAt;
//등록 / 삭제 요청 - return
public MemberResponseDto(Long id) {
this.id = id;
}
//Entity -> DTO
//조회 / 수정 요청 - return
public MemberResponseDto(Member entity) {
this.id = entity.getId();
this.username = entity.getUsername();
this.password = entity.getPassword();
this.email = entity.getEmail();
this.phone = entity.getPhone();
this.createdAt = entity.getCreatedAt();
this.updatedAt = entity.getUpdatedAt();
}
}
1. 회원 정보 수정 메서드
//Update (수정일자 업데이트)
public void update(MemberRequestDto dto) {
if (dto.getPassword() != null) {
this.password = dto.getPassword();
}
if (dto.getEmail() != null) {
this.email = dto.getEmail();
}
if (dto.getPhone() != null) {
this.phone = dto.getPhone();
}
}
Member Entity 에 회원 정보 수정 메서드(update) 를 작성하였다.
수정 값이 들어온 필드에 한해서 수정이 일어난다.
'password', 'email', 'phone' 수정에 모두 사용할 수 있다.
2. 회원 정보 수정 Service 기능 구현
//MemberService.class 中...
//Update Member(회원 정보 수정)
@Transactional
public MemberResponseDto updateMember(MemberRequestDto requestDto) {
//1. member 찾기
Member member = memberRepository.findById(requestDto.getId())
.orElseThrow(() -> new DataNotFoundException("존재하지 않는 회원 입니다."));
//2. update member
member.update(requestDto);
//3. member -> MemberResponseDto
return new MemberResponseDto(member);
}
3. 회원 정보 수정 Controller
이 부분 부터 아주 중요하다고 생각한다.
회원 정보 수정에 대한 @GetMapping, @PostMapping을 나눠 설명하겠다.
[@GetMapping - infoUpdateForm]
//MemberController.class 中...
/**
* member Information update
*/
@GetMapping("/info/update")
public String infoUpdateForm(Model model) {
Member currentMember = getCurrentMember();
MemberResponseDto responseDto = new MemberResponseDto(currentMember);
// username, createAt, updatedAt 을 보여주기 위함
model.addAttribute("responseDto", responseDto);
//responseDto -> requestDto
// email , phone 수정가능
MemberRequestDto requestDto = new MemberRequestDto(responseDto.getId(), responseDto.getEmail(), responseDto.getPhone());
model.addAttribute("requestDto", requestDto);
return "members/info-update";
}
Controller -> View 로 보내는 데이터는 크게 2가지 이다.
1. responseDto : id , username , password , email , phone , createdAt , updatedAt
2. resquestDto : id , email , phone
이렇게 구성되어 있다.
responseDto의 데이터는 수정이 불가하며, View 에 정보를 띄우는 용도이다.
requestDto의 데이터 중 email, phone 은 수정이 가능한 데이터이며,
id 는 데이터 수정을 위한 '회원 객체 식별자'로 사용된다.
[@PostMapping - updateMemberInfo]
//MemberController.class 中...
@PostMapping("/info/update")
public String updateMemberInfo(@ModelAttribute("requestDto") @Validated(MemberRequestDto.UpdateEmailPhone.class) MemberRequestDto dto,
BindingResult bindingResult, Model model) {
// 현재 로그인된 회원 객체
Member currentMember = getCurrentMember();
//유효성 검사 오류 발생시
if (bindingResult.hasErrors()) {
//기존 정보 그대로 반환 (수정 불가 정보 : username, createdAt, updatedAt)
MemberResponseDto responseDto = new MemberResponseDto(currentMember);
model.addAttribute("responseDto", responseDto);
//기존 정보 그대로 반환 (수정 가능 정보 : email, phone)
MemberRequestDto requestDto = new MemberRequestDto(responseDto.getId(), responseDto.getEmail(), responseDto.getPhone());
model.addAttribute("requestDto", requestDto);
//에러 메시지 반환
List<String> errorMassage = bindingResult.getAllErrors().stream()
.map(objectError -> objectError.getDefaultMessage())
.collect(Collectors.toList());
model.addAttribute("errorMessage", errorMassage);
return "members/info-update"; // 유효성 검사 실패 시 다시 폼으로
}
try {
//회원 정보 업데이트
memberService.updateMember(dto);
}
// 중복 검사 오류 시, 에러 처리 로직
catch (DataAlreadyExistsException | PasswordCheckFailedException ex) {
bindingResult.reject("errorMessage", ex.getMessage());
model.addAttribute("errorMessage", ex.getMessage());
return "members/info-update";
}
return "redirect:/members/info";
}
View -> Controller 로 받아오는 데이터는 "requestDto" 이다.
requestDto : id, email, phone
의 데이터가 들어있다.
유효성 검사, 중복 검사의 예외 처리에 해당하지 않는 경우
"memberService.updateMember(dto)" 를 통해 회원 정보 업데이트가 이루어진다.
4. 회원 정보 수정 VIEW (info-update.html)
<!--info-update.html 中...-->
<!-- Member Info Form -->
<div class="info-box">
<h2>Member Info</h2>
<form th:action="@{/members/info/update}" th:object="${requestDto}" method="post">
<!-- Hidden ID Field -->
<input type="hidden" th:field="*{id}">
<div>
<input id="info-username" th:value="${responseDto.username}" type="text" readonly>
<label for="info-username">Username</label>
</div>
<div>
<input id="info-email" th:field="*{email}" type="email" >
<label for="info-email">Email</label>
</div>
<div>
<input id="info-phone" th:field="*{phone}" type="text" >
<label for="info-phone">Phone</label>
</div>
<div>
<input id="info-created-at" th:value="${responseDto.createdAt}" type="text" readonly>
<label for="info-created-at">Created At</label>
</div>
<div>
<input id="info-updated-at" th:value="${responseDto.updatedAt}" type="text" readonly>
<label for="info-updated-at">Updated At</label>
</div>
<button type="submit" style="background: green;">Save</button>
<div th:if="${errorMessage}" style="color: red;">
<p th:text="${errorMessage}">Error message here</p>
</div>
</form>
</div>
구성은 다음과 같다.
1. responseDto : id , username , password , email , phone , createdAt , updatedAt
2. resquestDto : id , email , phone
이 두가지의 데이터가 Controller 단 -> VIEW 단 으로 왔다.
responseDto 의 데이터 중 'username' , 'createdAt' , 'updatedAt' 만 사용자에게 보여지고, 수정이 불가하다.
resquestDto 의 데이터 중 'id' 는 회원 정보 수정 식별자 로서 , 'hidden' 속성으로 사용자에게 보여지지 않는다.
'email' , 'phone' 속성은 수정 가능하며, 수정된 정보가 'th:field' 를 통해 resquestDto 에 저장된다.
'Save' 버튼을 누르면, <form th:action="@{/members/info/update}" th:object="${requestDto}" method="post"> 에 따라, post 요청이 보내진다.
5. 완성 화면
"member2@example.com" -> "member2@update.com"
Phone
"010-1111-1111" -> "010-9999-9999"
로 수정을 진행한다.
Email 과 Phone 이 정상적으로 수정된 것을 확인 할 수 있다.
Updated At (수정일자) 가 정상적으로 갱신된 것을 확인 할 수 있다.
DB 에서도 정상 반영이 된 것을 확인 할 수 있다.