[Spring Boot] 11. Spring Boot Project (Blog v2 - jpa)_6.User - Update

김미숙's avatar
Jul 22, 2025
[Spring Boot] 11. Spring Boot Project (Blog v2 - jpa)_6.User - Update

UserRequest

package shop.mtcoding.blog.user; import lombok.Data; public class UserRequest { //insert 용도의 dto에는 toEntity 메서드를 만든다 @Data public static class JoinDTO { private String username; private String password; private String email; public User toEntity() { return User.builder() .username(username) .password(password) .email(email) .build(); } } @Data public static class LoginDTO { private String username; private String password; private String rememberMe; // check되면 on 안되면 null } @Data public static class UpdateDTO { private String password; private String email; } }

UserController

package shop.mtcoding.blog.user; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import shop.mtcoding.blog._core.Resp; import java.util.Map; @RequiredArgsConstructor @Controller public class UserController { private final UserService userService; private final HttpSession session; @GetMapping("/join-form") public String joinForm() { return "user/join-form"; } @PostMapping("/join") public String join(UserRequest.JoinDTO joinDTO) { userService.회원가입(joinDTO); return "redirect:/login-form"; } @GetMapping("/check-username-available/{username}") public @ResponseBody Resp<?> checkUsernameAvailable(@PathVariable("username") String username) { Map<String, Object> dto = userService.유저네임중복체크(username); return Resp.ok(dto); } @GetMapping("/login-form") public String loginForm() { return "user/login-form"; } @PostMapping("/login") public String login(UserRequest.LoginDTO loginDTO, HttpServletResponse response) { User sessionUser = userService.로그인(loginDTO); session.setAttribute("sessionUser", sessionUser); if (loginDTO.getRememberMe() == null) { Cookie cookie = new Cookie("username", null); cookie.setMaxAge(0); // 즉시 삭제 response.addCookie(cookie); } else { Cookie cookie = new Cookie("username", loginDTO.getUsername()); cookie.setMaxAge(60 * 60 * 24 * 7); // cookie 하루동안 유지 response.addCookie(cookie); } return "redirect:/"; } @GetMapping("/logout") public String logout() { session.invalidate(); return "redirect:/login-form"; } @GetMapping("/user/update-form") public String updateForm() { // 인증로직 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("인증이 필요합니다."); // ViewResolver -> prefix = /templates/ suffix = .mustache return "user/update-form"; } @PostMapping("/user/update") public String update(UserRequest.UpdateDTO updateDTO) { // 인증로직 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("인증이 필요합니다."); User user = userService.회원정보수정(updateDTO, sessionUser.getId()); // session 동기화 -> 동기화 안해주면 바꾸기 전 정보를 보게 된다 session.setAttribute("sessionUser", user); return "redirect:/"; } }

UserService

package shop.mtcoding.blog.user; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.HashMap; import java.util.Map; // 비즈니스 로직, 트랜잭션 처리, DTO 완료 @RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; @Transactional public void 회원가입(UserRequest.JoinDTO joinDTO) { // 동일회원 있는지 검사 // User user = userRepository.findByUsername(joinDTO.getUsername()); // if (user != null) { // throw new RuntimeException("동일한 username이 존재합니다."); // } // 회원가입 userRepository.save(joinDTO.toEntity()); } public User 로그인(UserRequest.LoginDTO loginDTO) { // username,password 검사 User user = userRepository.findByUsername(loginDTO.getUsername()); if (user == null) { throw new RuntimeException("해당 username이 없습니다"); } if (!(user.getPassword().equals(loginDTO.getPassword()))) { throw new RuntimeException("해당 passward가 일치하지 않습니다"); } // 로그인 return user; } public Map<String, Object> 유저네임중복체크(String username) { User user = userRepository.findByUsername(username); Map<String, Object> dto = new HashMap<>(); if (user == null) { dto.put("available", true); } else { dto.put("available", false); } return dto; } @Transactional public User 회원정보수정(UserRequest.UpdateDTO updateDTO, Integer userId) { User user = userRepository.findById(userId); if (user == null) throw new RuntimeException("회원을 찾을 수 없습니다"); user.update(updateDTO.getPassword(), updateDTO.getEmail()); // 영속화된 객체의 상태변경 return user; } // 함수 종료될 때 더티 체킹(Dirty Checking) -> 상태가 변경되면 변경된 data를 가지고 update을 한다 }

Ditry Checkiing

엔티티의 값을 바꿨을 때, 개발자가 명시적으로 저장(save)하지 않아도 JPA가 알아서 변경 내용을 감지하고 DB에 반영해주는 기능

어떻게 동작하냐면?

  • JPA는 엔티티를 영속 상태로 만들면, 초기 상태의 스냅샷을 내부적으로 저장.
  • 트랜잭션이 커밋될 때, 엔티티의 현재 상태랑 스냅샷을 비교.
  • 값이 바뀌었으면 → 더럽다(dirty)고 판단 → UPDATE 쿼리 날림.

요약:

개념
설명
더티 체킹
영속성 컨텍스트 안에서 엔티티의 변경 사항을 자동으로 감지하고 DB에 반영하는 기능
이점
save() 같은 메서드 호출 없이도 DB 동기화 가능 → 코드 간결
조건
엔티티는 영속 상태여야 하고, 트랜잭션 내에서 작업해야 함

UserRepository

package shop.mtcoding.blog.user; import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @RequiredArgsConstructor @Repository public class UserRepository { private final EntityManager em; /* 1. createNativeQuery -> 기본 쿼리 2. createQuery -> JPA가 제공해주는 객체지향 쿼리 3. NamedQuery -> QueryMethod는 함수 이름으로 쿼리 생성 - 사용X 4. EntityGraph -> 지금 이해못함 */ public User findByUsername(String username) { try { return em.createQuery("select u from User u where u.username = :username", User.class) //객체지향 쿼리 .setParameter("username", username) .getSingleResult(); } catch (Exception e) { return null; } } public void save(User user) { em.persist(user); // insert query 발동 } public User findById(Integer id) { return em.find(User.class, id); // 영속화된 객체 } }

user/update-form

{{> layout/header}} <div class="container p-5"> <div class="card"> <div class="card-header"><b>회원수정을 해주세요</b></div> <div class="card-body"> <form action="/user/update" method="post" enctype="application/x-www-form-urlencoded"> <div class="mb-3"> <input value="{{sessionUser.username}}" type="text" class="form-control" placeholder="Enter username" disabled> </div> <div class="mb-3"> <input type="password" class="form-control" placeholder="Enter password" name="password"> </div> <div class="mb-3"> <input value="{{sessionUser.email}}" type="email" class="form-control" placeholder="Enter email" name="email"> </div> <button type="submit" class="btn btn-primary form-control">회원가입수정</button> </form> </div> </div> </div> {{> layout/footer}}
 
notion image
notion image
Share article

parangdajavous