유저기능은 다 구현한 줄 알았는데 유저 수정하기와 삭제가 남아있었다. 유저 수정하는게 본인은 수정할 수 있어야 하고, 관리자만 모든 유저를 수정할 수 있어야 하기 때문에 어드민 권한은 어드민 테이블 완성 후에 하기로 하고 자신의 정보만 수정하는 기능을 만들었다.
처음에 먼저 HTML을 복사해준다. ( HTML 만들기 귀찮 .. )
그리고 원래 하듯이 컨트롤러에서 Get방식으로 통신을 하여 model에 userForm을 넘겨준다. 그리고 데이터를 받아서 @ModelAttribute로 form을 받아오고 변경감지를 이용해 데이터를 변경하도록 한다.
[ 변경감지를 이용한 업데이트 메서드 만들기 ]
변경감지는 말 그대로 JPA가 변경 감지를 하는 것이다. 원래 엔티티는 영속성 컨텍스트 안에 있는데, 이게 DB를 들어갔다 나오면 영속성 컨텍스트 안에 있지 않다. 그래서 이런 데이터를 준영속 엔티티라고 한다. 영속성 데이터는 JPA가 관리하기 때문에 데이터가 변경이 되면 자동으로 업데이트 쿼리를 날려준다. 하지만 준영속 엔티티는 JPA가 관리하는 영역 밖에 있기 때문에 업데이트를 따로 해주거나 영속성 데이터로 만들어야 한다. 여기서 나는 업데이트 쿼리를 짜고 뭐 어쩌구 하는 것보다 영속성 데이터로 만드는 것이 더 편하다고 배웠기 때문에 영속성 데이터로 바꿔서 JPA가 자동으로 업데이트 쿼리를 날리게 해주는 "변경감지"를 사용했다.
식별자(PK)를 이용해 "트랜잭션" 안에서 엔티티를 조회하고 변경할 값을 조회한 엔티티에 넣어준다 (=변경). 그렇게 되면 트랜잭션 커밋 시점 (메서드 종료시점)에 변경감지가 동작해 DB에 JPA가 업데이트 SQL를 실행한다.
@Transactional(readOnly = false) // Dirty Check
public void updateUser(User updateData) {
User user1 = userRepository.findOne(updateData.getId());
if (updateData.getName() != null) {
user1.changeName(updateData.getName());
}
if (updateData.getLoginId() != null) {
user1.changeLoginId(updateData.getLoginId());
}
if (updateData.getLoginId() != null) {
user1.changeLoginPw(updateData.getLoginPw());
}
if (updateData.getSchoolCategory() != null) {
user1.changeSchoolCategory(updateData.getSchoolCategory());
}
}
나는 선택적으로 데이터를 수정할 수 있도록 만들고 싶었기 때문에 다 조건을 걸어서 변경하도록 했다. 변경감지에서 주의해야 할 점은 데이터 조회가 "트랜잭션 안에서" 이루어져야 한다는 것이다.
[ 테스트 코드 작성하기 ]
간단하게 빌더로 유저 데이터를 하나 회원가입 시킨 후에 값을 바꿔서 같은 식별자값으로 설정한 후 메서드를 이용해 업데이트 해주고 나서, 식별자를 이용해 데이터를 찾아서 값이 변경되었는지 확인하였다.
@Test
public void updateUserTest() {
String name = "Test";
String loginId = "Test";
String loginPw = "Test";
User user1 = User.builder()
.name(name)
.loginId(loginId)
.loginPw(userRepository.encryption(loginPw))
.build();
userService.register(user1);
String name2 = "Complete";
String loginId2 = "Complete";
User user = User.builder()
.id(user1.getId())
.name(name2)
.loginId(loginId2)
.loginPw(userRepository.encryption(loginPw))
.build();
userService.updateUser(user);
em.flush();
User result = userRepository.findOne(user1.getId());
if (result.getName() == name2) {
return;
}
User temp = userRepository.findOne(2L);
if (temp.getName() == null) {
return;
}
fail("실패");
}
[ . . . ]
업데이트 메서드를 만들어줬기 때문에 그대로 업데이트를 날리려고 했으나 생각해보니 대상을 어떻게 지정할지를 생각하지 못했다. 그래서 보통 사이트들이 쓰는 것처럼 로그인하면 자동으로 오른쪽 상단에 자기 아이디가 뜨거나, 어떤 작업을 할 때 자신의 정보를 입력하지 않아도 자신의 정보가 자동 입력되도록 하기로 했다.
[ 쿠키 이용하여 아이디 가져오기 ]
쿠키란 쉽게 말하면 작은 DB파일이 지정된 기간동안 컴퓨터에 설치되는 것을 말한다. 나는 로그인 시점부터 쿠키를 사용하도록 할 것이기 때문에 로그인 컨트롤러로 가서 코드를 추가해주었다.
// 쿠키 처리
Cookie cookie = new Cookie("Id", String.valueOf(user.getId()));
response.addCookie(cookie);
쿠키에 키값을 입력하고 HttpServletResponse에 추가해주는 코드이다. 키는 Id, 값은 유저의 식별자(ID)이다. 이렇게 되면 로그인 이후에 해당 사용자의 ID가 컴퓨터에 남게 된다. 그리고 잘 작동하는지 확인하기 위해 업데이트 컨트롤러에서 Get방식으로 form을 넣을 때, model에 쿠키 Id값도 같이 넣어서 화면에 표시하도록 했다.
@GetMapping("/user/userUpdate")
public String updatePage(Model model,
@CookieValue(value = "Id", required = false) String Id) {
if (Id == null || Id.isEmpty()) {
return "redirect:/";
}
Long id = Long.parseLong(Id);
User user = userRepository.findOne(id);
model.addAttribute("loginId", user.getLoginId());
model.addAttribute("updateForm", new UserForm());
return "user/UserUpdate";
}
그 후 Post방식으로 데이터를 받아오고, Test와 같이 엔티티를 하나 만든 후에 메서드를 이용해 데이터를 건네줬다.
@PostMapping("/user/userUpdate")
public String userUpdate(@ModelAttribute UserForm form,
@CookieValue(value = "Id") String Id) {
Long id = Long.parseLong(Id);
User user2 = User.builder()
.id(id)
.name(form.getName())
.loginId(form.getLoginId())
.loginPw(form.getLoginPw())
.build();
userService.updateUser(user2);
return "redirect:/";
}
[ 문제의 시작 ]
그런데 . . . 분명히 테스트에선 잘 작동하고 em.flush로 쿼리를 살펴봐도 업데이트 쿼리가 잘 작동하는데 왜인지 직접 사이트에 들어가 수정을 하면 UPDATE가 아닌 INSERT쿼리가 DB에 날라가 그냥 유저가 하나 더 추가되어버린다.
[ 문제 해결 ]
그래서 테스트코드가 잘못된 거였나 하고 테스트 코드도 다시 써보고 . . 쿠키가 잘못된 건가 하고 쿠키 코드를 지워서도 해보고 .. 여러 별 가지 수를 다 써봤는데 똑같이 INSERT문이 작동되었다. 거기다 엎친 데 덮친 격으로 이상한 오류가 떴다.
구글링을 해보니 예기치 않은 오류로 인해 종료됐을 때 뜬다는데 .. 실질적인 해결법은 아무리 검색해도 안 나왔다.
그렇게 몇시간을 헤매다가 구글링을 통해 위의 오류는 무시해도 되는 오류였다. 나랑 똑같이 떴던 분이 인프런에 질문을 올리셨는데 , 디버그라서 괜찮다는 답변이 있었다. 그래서 이 오류는 무시하고 진행했다. 그 후에 계속 코드를 살펴보고, 오류 내용을 잘 살펴보다 보니, 회원가입으로 들어가서 값을 넣은 후에 실행이 되어서 INSERT문이 들어간다는 것을 알게 되었다. 그래서 바로 HTML 코드를 살펴보니 action이 회원가입 경로로 되어있었다 .. 진짜 이거 딱 보고 한숨이 푹 나왔다 .. 지금도 생각하면 한숨만 나오지만 그래도 내일도 이 오류만 보지 않게 되어서 좋았다.
[ 로그아웃 및 쿠키 삭제하기 ]
몇 시간을 헤매다가 새로운 기능을 구현하려 하니 너무 기분이 좋았다. 기분이 좋았는지 금방 구현했다. 실제로 별거 아니기도 했고 코드 수도 적어서 방법을 생각해낸 후에 바로 코드를 적어서 바로 성공한 것 같다.
쿠키는 위에서 말했듯이 "정해진 기간"동안 파일이 있는 것이기 때문에 이 기간을 0으로 줄인다면 쿠키 삭제나 다름없게 된다.
@GetMapping("/user/logout")
public String logoutPage(HttpServletResponse response) {
Cookie cookie = new Cookie("Id", null);
cookie.setMaxAge(0);
response.addCookie(cookie);
return "redirect:/";
}
[ 후기 ]
지금까지 간단한거에 몇시간을 헤맸다는 글이나 얘기를 너무 많이 들었어서 주의하면서 코딩을 했었는데, 웹사이트 개발을 시작하면서 이런 일이 잦아진 것 같다. 새로운 언어를 배워서 그런가 매일매일 새로운 지식이 들어오니 주의력이 떨어진 것 같다. 다시금 코드를 잘 보면서 코딩을 해야겠단 생각을 하면서 기능 구현을 마쳤다. 내일은 간단해보이는 유저 삭제와 드디어 게시물 기능을 구현하기 시작할 것 같다.
'개발일지_development diary > YSit' 카테고리의 다른 글
YSit [11] - 게시물 작성 / 목록 기능 구현 (0) | 2022.12.28 |
---|---|
YSit [10] - 세션 사용하여 쿠키 대체하기 (0) | 2022.12.28 |
YSit [8] - UserController 개발하기 2 (0) | 2022.12.24 |
YSit [7] - UserController 개발하기 (0) | 2022.12.24 |
YSit [6] - 유저 기능 테스트하기 (0) | 2022.12.24 |