v2 - 삭제하기

coding S's avatar
Mar 14, 2024
v2 - 삭제하기
1번을 삭제하려고 할 때, 방법 2가지

1. BR에서 1번(pk)을 강제로 집어 넣어서 비영속 객체를 만들어 처리 → 쓰지마

1.

notion image

2.

notion image
💡
있으면 remove. 없으면 null이 들어갈 것. (null처리는 어떻게 하지? -> try-catch)
new Board 로 비영속 보드객체 만들고 프라이머리 키를 강제로 ID 만들고 (setId) remove에서 Board id가 1번인 걸 찾아서 삭제 함 -> 없을 수도 있기 때문에 추천하지 않음!!!
💡
또다른 이유로는… deleteById는 다른 곳에서도 사용할 수 있으니 … 이렇게 해버리면 재사용이 안 된다는 듯 ?
 

레파지토리는 이렇게 쓰는 걸 추천!

notion image
값이 있든 없든 delete의 목적은 그저 삭제하는 것. null이면 이렇게 하고, 아니면 이렇게 하고... 이런 구구절절 비즈니스 로직은 이쪽이 아니라 서비스 레이어 (트랜젝션을 처리함) 에서 해줘야 함. -> 레파지토리는 db에 질의하는 역할. 딱 자기 책임만 질 수 있도록 해줘야함!
💡
:id는 JPQL(Java Persistence Query Language) 쿼리에서 사용되는 이름 기반 매개변수. 이 매개변수는 쿼리를 실행할 때 동적으로 값을 바인딩할 수 있게 해준다. (여기선 setParameter id 값을 바인딩하는 듯!) :id 같은 이름 기반 매개변수는 ?를 사용하는 위치 기반 매개변수의 대안으로 사용된다, 즉, ?의 자리를 안정해주고 바로 적으면 되는거인 듯!
notion image
💡
주석 처리한 저것(보라색 상자)은 서비스 레이어 쪽에 쓰자
 

BoardPersistRepository

@RequiredArgsConstructor @Repository public class BoardPersistRepository { private final EntityManager em; @Transactional //이거 안 쓸 거다. 밑에 있는 deleteById를 사용할 것이다! public void deleteByIdV2(int id) { Board board = findById(id); em.remove(board); //근데 remove가 어떻게 동작하는지 궁금하니 테스트 해보자! } @Transactional public void deleteById(int id){ Query query = em.createQuery("delete from Board b where b.id = :id"); query.setParameter("id", id); query.executeUpdate(); }
 

[ remove Test ]

@Test public void deleteByIdV2_test() { int id = 1; boardPersistRepository.deleteByIdV2(id); }
notion image

왜 delete를 안 하지? → 트랜젝션 때문! (delete의 특징)

delete라는 애는 트랜젝션이 종료될 때 쿼리가 날아가는 특징이 있다. deleteByIdV2를 호출했을 때, 트랜젝션이 종료되는 시점에서 쿼리가 날아간다. 왜 지금 안날라가? 자식 트랜젝션이어서 !! (트랜젝션이 2개 걸려있음) 단위 테스트를 할 때에는 deleteByIdV2_test 라는게 실행될 때, 트랜젝션이 발동한다. @DataJpaTest는 메타 어노테이션으로 @Transactional을 들고 있다. @Transactional의 기본 -> commit, rollback, 원자성 (원자성 - 전부 다 실행되면 commit 하나라도 안되면 rollback) 만약 찾지 못하고 예외가 발생하면 자동으로 rollback 해줌. (내가 이런 식으로 트랜젝션 코드를 적지 않아도!)
notion image
💡
아유 복잡해 ! 스프링은 이런 행위를 용납하지 않는다. 초보자를 위해서 스프링이 제공하는 어노테이션을 쓰라고 강제한다.
notion image
💡
그래서 이렇게 쓸 수 있다 ~~
 

[ 테스트 코드에서… 자식 트랜젝션 ? 중첩 트랜젝션? ]

notion image
notion image
💡
DataJpaTest에서 @Transactional를 가지고 있음. → 메타 어노테이션
💡
메타 어노테이션이란? → 어노테이션에 대한 어노테이션
notion image
notion image
💡
애도 가지고 있음. 중첩 @Transactional !!

테스트 코드는

테스트 코드가 실행되는 begin 직전에 트랜젝션이 걸린다. 메소드가 끝날 때, 트랜젝션이 종료되는데 테스트라 기록해 놓을 필요가 없어서 디폴트가 rollback!! 이다. 테스트 코드는 중첩 트랜젝셔널이 걸려있다. -> 자식 트랜젝션 단위 테스트 할 때는 내부의 트랜젝션(자식. 레파지토리를 뜻하는 듯)은 무시된다. 외부 트랜젝션 (테스트 트랜젝션) 에게 우선권이 있다. [ 그냥 Repository에서 했을 때 ] BC가 BR을 호출하면 레파지토리에서 메서드가 실행되고, findById로 Board 객체를 찾는다. em.remove로 PC에서 삭제는 했으나 쿼리는 날아가지 않은 상태. 응답이 되는 순간 커밋이 되면서 쿼리가 날아감. -> 컨트롤러에서 트랜젝션이 종료되기 때문에 똑같은걸 findById로 remove를 하면, 아직 트랜젝션 종료가 안 되었기 때문에 (쿼리문이 안 날아가서. 트랜젝션 종료는 BC에서 종료되니까) 아직 DB에는 살아있는 상태. 그래서 삭제를 했는데도 조회가 된다 !
💡
삭제는 그렇다!!!!!!!!!! 조회는 안 이런다.
 

해결법 - em.flush();

트랜젝션 종료는 안 되었지만 내가 강제로 쿼리를 날려주면 된다.
@Test public void deleteByIdV2_test() { // given int id = 1; //when boardPersistRepository.deleteByIdV2(id); // 이 라인 쿼리. 트랜젝션 종료되지 않았지만 강제로 날려보냄 em.flush(); }
notion image
 

테스트 해보기

@Test public void deleteByIdV2_test() { // given int id = 1; //when boardPersistRepository.deleteByIdV2(id); // 이 라인 쿼리. 트랜젝션 종료되지 않았지만 강제로 날려보냄 // em.flush(); Board board = boardPersistRepository.findById(id); System.out.println("findById_test " + board); }
notion image
왜 null로 뜰까? DB에는 삭제되지 않았는데... PC가 똑똑해가지구... 실제 DB에는 삭제되지 않았는데 어짜피 이거 끝나면 삭제되는거 맞잖아 ㅎ 해서 자기가 알아서 null값 (삭제 되었어요~) 하고 보내주는 것 -> 커밋되기 전 데이터를 읽고 있다
💡
이 안에서 종료되어도 test의 트랜젝션을 사용하고 있기 때문에 쿼리가 안 날아감 눈으로 보기 위해서 em.flush()를 사용함
 
 

BoardController

@PostMapping("/board/{id}/delete") public String delete(@PathVariable Integer id){ boardPersistRepository.deleteById(id); return "redirect:/"; }
 

[ 핵심 내용 정리 ]

테스트 코드 실행 시점에 트랜잭션이 시작되고, 테스트가 끝날 때 트랜잭션이 종료되며(롤백되는 것이 기본 설정) 이는 테스트가 데이터베이스에 영구적인 변경을 남기지 않도록 함으로써 -> 다른 테스트에 영향을 주지 않게 하기 위함이다.
중첩 트랜잭션, 즉 자식 트랜잭션이 있는 경우, 단위 테스트 환경에서는 이 자식 트랜잭션이 무시되고 외부의 테스트 트랜잭션이 우선권을 가지게 된다. -> 테스트 환경에서의 일관성과 격리를 유지하기 위함
💡
em.remove = PC에 객체를 지우고, 트랜젝션이 종료되면 삭제 쿼리를 전송함 em.flush = 버퍼가 쥐고 있는 쿼리를 즉시 전송 (테스트 할 때만 사용 가능) 본코드에서 사용 시, 왜 강제로 플러시해? 트랜젝션이 종료되면 스프링인 내가 알아서 해줄 건데! 하고 거절함 em.clear = 삭제되는 건 아니고, 비우는 것
💡
핵심 트랜젝션이 종료될 때, 쿼리가 날아간다 트랜젝션이 종료 되어야지 영구히 기록(commit)이 된다. commit이 되기 전에는 메모리에 남아있다.
[ read 전략 ] 커밋이 된 것만 읽을까? 커밋이 안 된 것을 읽을까? 설정할 수 있다. (redo(다시하기), undo(되돌리기)) if) 이체라면 업데이트를 2번 치는게 트랜젝션. 업데이트를 1번 쳤는데, 누가 들어와서 계좌 내역을 조회한 것임. 지금 a라는 애가 접속해서 b한테 1000원을 보냄. a는 0원, b는 2000원이 되어야하는데, a가 0원이란 업데이트만 되었을 때, 다른 사람이 들어옴 -> 트랜젝션 완료가 안 된 상태 -> 아, 이거는 커밋이 된 것만 읽게 해야겠다!! -> 메모리 자체는 0원 천원 (업데이트가 1개만 된 상태) -> 언두는 천원 천원 (데이터의 정확성이 안 깨짐) -> db설정할 때 이걸 골라서 읽는(?) 전략
💡
고립 전략은 이거 2가지 뿐만 아니라 여러가지가 있다. → 4가지 전략…
 
💡
실제 레파지토리에서는 메소드가 종료되면 트랜젝션이 종료되고 커밋이 됨 테스트 코드에서는 롤백이 됨 → 쿼리도 안 날아감, 다시 데이터가 돌아감. → em.flush를 붙이면 쿼리가 날아가고, 롤백이 됨. 롤백 = 메모리 상에 있는 걸 undo 데이터로 돌리는 것 커밋 = 메모리 상에 있는 걸 하드 디스크에 저장하는 것
 
 
Share article

codingb