블로그 만들기 12. 로그아웃 구현 : Transaction

Feb 05, 2024
블로그 만들기 12. 로그아웃 구현 : Transaction

1.트랜잭션(Transaction)
  • 일의 최소 단위 : 일은 상대적인 것
  • 현재 상태를 변경 불가능한 상태로 고정시키는 것 = 타임 아웃 같은것(시간이 멈춤)
  • write 요청 시 다른 애들이 못하고 한 명이 독립적으로 insert 사용 가능함
다른 애들이 DB 변형할 때 rock이 걸림
  • 트랜잭션은 응답하면 끝남
 

2. 스프링에서는 여러가지 작업을 트랜잭션 걸기

  • 요청 > 응답하고 종료가 아니라 쥐고 있을 수 있음
💡
왜 트랜잭션을 안 끈기게 하게 할까?
한번에 안 끝나는 트랜잭션도 있기 때문!
ex) 입금은 내 금액만 수정
이체는 내 금액과 상대방의 금액을 수
둘 다 1000원씩 있는데 A가 B한테 1000원을 이체 요청함
트랜잭션을 걸어서 A만 가능하고 나머지는 wait 걸림
update 문으로 wait 요청하고 B의 금액을 수정하고 트랜잭션이 끝이 나면
A의 금액을 수정를 하지 못하고
다른 C가 이체 요청을 해서 A의 1000원 금액을 가져갈 수 있음
은행의 입장에서는 없는 A의 금액을 이체 해줄 수 없기에 A가 B에게 이체해주는 것이 안됨
  • update : 실제로 하드디스크에 기록되는 것이 아니라 메모리에만 기록된 것
  • commit : 영구히 기록하기 위해 하는 것
  • select문에는 안걸어도 되고 나머지 DB에 변동하는 쿼리문에는 걸어야 함

3. findByUsernameAndPassword() 구현하기

package shop.mtcoding.blog.user; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @Repository // IoC에 new하는 방법 public class UserRepository { // DB에 접근할 수 있는 매니저 객체 // 스프링이 만들어서 IoC에 넣어둔다. // DI에서 꺼내 쓰기만 하면된다. private EntityManager em; // 컴포지션 // 생성자 주입 (DI 코드) public UserRepository(EntityManager em) { this.em = em; } public boolean UserRepository(String username) { // 생성자 Query query = em.createNativeQuery("select * from user_tb where username=?", User.class); query.setParameter(1, username); try { User user = (User) query.getSingleResult(); return true; // 있음 } catch (Exception e) { return false; // 없음 } } @Transactional // DB에 write 할때는 필수 public void save(UserRequest.joinDTO requestDTO) { // 컨트롤러는 정보를 전달하면서 때리고 모델에 위임함 // DB에 INSERT할 쿼리문 작성 Query query = em.createNativeQuery("insert into user_tb(username, password, email, created_at) values(?,?,?, now())"); // 가방에 담긴 정보를 DB에 담기 query.setParameter(1, requestDTO.getUsername()); query.setParameter(2, requestDTO.getPassword()); query.setParameter(3, requestDTO.getEmail()); query.executeUpdate(); } public User findByUsernameAndPassword(UserRequest.loginDTO requestDTO) { Query query = em.createNativeQuery("select * from user_tb where username=? and password=?", User.class); query.setParameter(1, requestDTO.getUsername()); query.setParameter(2, requestDTO.getPassword()); User user = (User) query.getSingleResult(); return user; } public User findByUsername(String username) { Query query = em.createNativeQuery("select * from user_tb where username=?", User.class); query.setParameter(1, username); try { User user = (User) query.getSingleResult(); return user; }catch (Exception e){ return null; } } }
  • -1이 떨어지면 전체를 롤백해야 함
트랜잭션 안에서 했던 모든 것들은 아직commit 이 안된 상태
메모리를 초기화하는 것 : 메모리를 원 상태로 바꾸는 것
 
ex) 1000원씩 있는데 메모리에 기억하고 있음
하나를 0으로 바꾸고 다른 애를 2000원으로 바꿔야 할 때 따로 기억하는 것 : 언두(undo)=취소
트랜잭션에서 이전 상태로 롤백하는 기능
다시 요청했는데 실패할 경우
언두(에 있는 데이터를 가지고 와서 덮어 씌우는 것)해야 함 ⇒ 원자성
  • 원자성(Atomicity) : 트랜잭션이 성공적으로 완료 or 실패할 경우 롤백되어야 함
될거면 다되고 안되면 처음으로 돌아가는 것
 
💡
실패하는 트랜잭션을 만들지 말라는 것!
insert(회원가입)하기 전에 터지면 다른 애들이 wait가 걸리니까
select 요청해서 확인하고 insert 하는 것이 안전함
 
  • read할 때부터 트랜잭션을 걸면 절대 아무도 못 건드림!
write할 때만 걸면 확인하고 insert하러 갔더니 누가 했을 수도 있음 / 팬텀 데이터 문제
  • read, write를 트랜잭션으로 묶을 수 있음 → 유령 데이터(팬텀 데이터)이 안보임
트랜잭션 내에서 데이터의 일관성을 유지할 수 있음
작업이 완료되기전에 다른 작업에 의해 변경되지 않음
  • 스프링에 서비스를 관리하는 레이어(Service)가 더 필요함
트랜잭션을 걸어야 함
나머지가 wait 걸리니까 트랜잭션 시간을 줄여야함
@Transactional
 
  • 유효성 검사 전에 save 메서드 안에서 트랜잭션을 걸면 좋지 않음
독립적으로 두 번 걸리는 것이기 때문
→ read와 write가 따로 돌게 되고 틈이 생기게 됨
하나로 묶을 서비스 레이어가 필요함
  • 최소한의 단위를 트랜잭션으로 묶을 수 있음
username 체크 위임을 다른 클래스로 옮겨서 트랜젝션을 걸 수 있음
 

4. 게시판에 로그인한 사람 확인하고 지우기

  • index.mustache 에 가정법을 사용하여 username 표시하기
{{> /layout/header}} <h1> {{#sessionUser}} {{username}} {{/sessionUser}} </h1> <div class="container p-5"> <div class="card mb-3"> <div class="card-body"> <h4 class="card-title mb-3">제목1</h4> <a href="/board/1" class="btn btn-primary">상세보기</a> </div> </div> <ul class="pagination d-flex justify-content-center"> <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li> <li class="page-item"><a class="page-link" href="#">Next</a></li> </ul> </div> {{> /layout/footer}}
notion image
 
 

4. 키 자체의 값이 있는 서랍 지우기

package shop.mtcoding.blog.user; 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.PostMapping; @RequiredArgsConstructor @Controller public class UserController { // fianl 변수는 반드시 초기화 되어야 함 private final UserRepository userRepository; // null private final HttpSession session; @PostMapping("/login") public String login(UserRequest.loginDTO requestDTO) { System.out.println(requestDTO); // 1. 유효성 검사 if (requestDTO.getUsername().length() < 3) { return "error/400"; } // 2. 모델 필요 select * from user_tb where username=? and password=? User user = userRepository.findByUsernameAndPassword(requestDTO); if (user == null) { return "error/401"; } else { return "redirect:/"; } } @PostMapping("/join") public String join(UserRequest.joinDTO requestDTO) { System.out.println(requestDTO); //@DATA안에 String도 포함되어있음 // 1. 유효성 검사 if (requestDTO.getUsername().length() < 3) { return "error/400"; } // 2. 동일 username 체크 User user = userRepository.findByUsername(requestDTO.getUsername()); if (user == null) { // 3. Model에게 위임하기 try { // 터트린 것을 잡겠다? userRepository.save(requestDTO); // 경우의 수가 너무 다양함 : 에러를 명확하게 판단하기 힘듦 } catch (Exception e) { } } else { return "error/400"; } return "redirect:/loginForm"; //리다이렉션불러놓은게 있어서 다시 부른거 } @GetMapping("/joinForm") public String joinForm() { return "user/joinForm"; } @GetMapping("/loginForm") public String loginForm() { return "user/loginForm"; } @GetMapping("/user/updateForm") public String updateForm() { return "user/updateForm"; } @GetMapping("/logout") public String logout() { // 1번 서랍에 있는 uset를 삭제해야 로그아웃이 됨 session.invalidate(); // 서랍의 내용 삭제 return "redirect:/"; } }
 

5. 톰캣의 스코프(생명주기)

  • 애플리케이션 > 세션 > 리쿼스트
  • 세션
로그인하고 30분 후, 클라이언트가 JSESSIONID 삭제, SESSION에서 서랍 날리기
  • 리쿼스트 : 응답받고 끝
Share article

vosw1