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}}


Share article