1. remove 사용하기
- 1번을 삭제하기
- 1번에 강제로 insert해서 비영속 객체를 하나 만듦 → 없을 수도 있으니 위험함
- 1번을 조회해서 있으면 removw, 없으면 null
- delete 쿼리 작성하기
2. 트랜잭션(Transaction)
- 하나 이상의 SQL 명령을 포함하는 논리적 작업 단위
- 원자성(Atomicity), 일관성(Consistency), 독립성(Isolation), 지속성(Durability) 보장
- 커밋(Commit) : 트랜잭션이 성공적으로 완료되어서 변경 사항을 DB에 영구적으로 저장
- 롤백(Rollback : 트랜잭션이 실패 or 취소되어야 할 때 변경 사항을 취소하는 작업
이전 상태로 되돌리는 역할을 합니다.
- 커밋과 롤백의 기본 동작:
일반적으로 트랜잭션은 메서드나 비즈니스 로직의 실행에 의해 시작
성공적으로 완료되면(모든 작업이 커밋되면) 커밋이 발생, 변경 사항이 영구적으로 저장
런타임 예외가 발생하면 트랜잭션은 롤백되어 이전 상태로 되돌아감
- 트랜잭션의 커밋과 롤백 처리
자바에서는 보통 JDBC(Java Database Connectivity)나 JPA(Java Persistence API) 등을 사용
트랜잭션은 보통 메서드 호출과 함께 시작, 커밋 or 롤백은 트랜잭션이 종료되는 시점에 발생
- 자식 트랜잭션
일부 트랜잭션 관리 시스템에서는 자식 트랜잭션을 지원
하나의 트랜잭션 내에서 여러 개의 서브 트랜잭션을 생성
각 서브 트랜잭션을 독립적으로 커밋하거나 롤백할 수 있는 기능을 제공
3. 테스트 환경에서의 트랜잭션
- 테스트 메서드 실행 : 단위 테스트 메서드를 실행
메서드는 트랜잭션 내에서 실행되며, 테스트하려는 로직이 포함
- 트랜잭션 시작 : 테스트 메서드가 시작될 때, 보통 트랜잭션이 시작
- 자식 트랜잭션 생성 : delete문을 실행하는 도중에 자식 트랜잭션을 생성 가능
특정 delete 작업을 트랜잭션으로 감싸서 독립적으로 커밋하거나 롤백 가능
- delete문 실행 : 자식 트랜잭션 내에서 delete문이 실행
특정 조건에 따라 DB에서 레코드를 삭제
- 자식 트랜잭션 커밋 or 롤백 : delete문이 실행된 후 자식 트랜잭션은 결과에 따라 커밋 or 롤백
- 트랜잭션 종료 : 테스트 메서드가 완료되면 트랜잭션이 종료
변경된 데이터는 롤백 or 커밋되며, 테스트 환경은 초기 상태로 복원
우선순위 : 외부 트랜잭션 > 내부 트랜잭션
- 내부 트랜잭션이 외부 트랜잭션의 범위 내에서만 존재 / 외부 트랜잭션의 커밋 or 롤백에 종속
- 내부 트랜잭션 : 외부 트랜잭션의 일부로서 독립적인 작업 단위
특정 작업을 별도의 트랜잭션으로 처리, 필요한 경우에만 커밋 or 롤백 때 사용
외부 트랜잭션의 범위 내에서만 유효, 외부 트랜잭션의 시작과 종료에 의존
- 외부 트랜잭션의 커밋 또는 롤백에 따라 내부 트랜잭션의 상태가 변경
외부 트랜잭션이 롤백되면 내부 트랜잭션도 롤백
외부 트랜잭션이 커밋되면 내부 트랜잭션도 커밋
내부 트랜잭션은 외부 트랜잭션의 상태에 의존하여 처리
4. BoardPersistRepository 에 deleteById() 만들기
- delete : 트랜잭션이 종료될 때 쿼리가 날아감
- 트랜잭션의 기본은 커밋 or 롤백
- 메서드가 종료될 때 트랜잭션이 종료
- 기본이 커밋, 실행되다 런타임 예외가 발생한 경우(결과가 없을 때) 롤백
- 자식 트랜잭션일 때 설정하는 기술들이 있음
package shop.mtcoding.blog.board; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import java.util.List; @RequiredArgsConstructor @Repository public class BoardPersistRepository { private final EntityManager em; // @Transactional // public void deleteByIdv2(Integer id) { // Board board = findById(id); // em.remove(board); // PC에 객체를 지우고, (트랜잭션 종료 시) 삭제 쿼리를 전송함 // } @Transactional public void deleteById(Integer id) { Query query = em.createQuery("delete from Board b where b.id = :id"); query.setParameter("id", id); query.executeUpdate(); } public Board findById(Integer id) { Board board = em.find(Board.class, id); // (class명, PK) return board; } public List<Board> findAll() { Query query = em.createQuery("select b from Board b order by b.id desc", Board.class); return query.getResultList(); } @Transactional public Board save(Board board) { // PC에 Data 주소 넣기(Entity만 가능함) em.persist(board); // 실행후 영속 객체가 됨 return board; } }
- 단위 테스트하기(deleteById, deleteByIdv2)
package shop.mtcoding.blog.Board; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; import shop.mtcoding.blog.board.Board; import shop.mtcoding.blog.board.BoardPersistRepository; import java.util.List; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @Import(BoardPersistRepository.class) @DataJpaTest public class BoardPersistRepositoryTest { @Autowired //DI private BoardPersistRepository boardPersistRepository; @Test public void deleteById_test(){ // given int id = 1; // when Board board = boardPersistRepository.findById(id); boardPersistRepository.deleteById(id); // then List<Board> boardList = boardPersistRepository.findAll(); System.out.println("deleteById_test/size : " + boardList.size()); } @Test public void findById_test() { //given int id = 1; //when Board board = boardPersistRepository.findById(id); boardPersistRepository.findById(id); System.out.println("findById_test : " + board); } @Test public void findAll_test() { //given - 지금은 넣을게 없음 //when List<Board> boardList = boardPersistRepository.findAll(); //then System.out.println("findAll_test/size : " + boardList.size()); System.out.println("findAll_test/username : " + boardList.get(2).getUsername()); //org.assertj.core.api Assertions.assertThat(boardList.size()).isEqualTo(4); Assertions.assertThat(boardList.get(2).getUsername()).isEqualTo("ssar"); } @Test public void save_test() { //given Board board = new Board("제목5","내용5","ssar"); //when boardPersistRepository.save(board); //then System.out.println(board); } }
- 아직 트랜잭션이 종료되지 않았기 때문에 롤백되어 elete 쿼리가 전송되지 않음
@Test public void deleteByIdv2_test(){ // given int id = 1; // when boardPersistRepository.deleteByIdv2(id); }
- 강제로 전송하기
@Test public void deleteByIdv2_test(){ // given int id = 1; // when boardPersistRepository.deleteByIdv2(id); em.flush(); // 버퍼에 쥐고 있는 쿼리를 즉시 전송 }
- clear는 PC를 다 비우는 것
- remove는 PC에서 한 개를 지우는 것
5. BoardController 에서 delete 수정하기
package shop.mtcoding.blog.board; import ch.qos.logback.core.model.Model; import jakarta.servlet.http.HttpServletRequest; 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 java.lang.annotation.Native; import java.util.List; @RequiredArgsConstructor @Controller public class BoardController { private final BoardNativeRepository boardNativeRepository; private final BoardPersistRepository boardPersistRepository; @PostMapping("/board/{id}/update") public String update(@PathVariable Integer id, String title, String content, String username){ boardNativeRepository.updateById(id, title, content, username); return "redirect:/board/"+id; } @GetMapping("/board/{id}/update-form") public String updateForm(@PathVariable (name="id") Integer id, HttpServletRequest request) { Board board = boardNativeRepository.findById(id); request.setAttribute("board", board); return "/board/update-form"; // 서버가 내부적으로 index를 요청 - 외부에서는 다이렉트 접근이 안됨 } @PostMapping("/board/{id}/delete") public String delete(@PathVariable Integer id) { // DTO 없이 구현 boardPersistRepository.deleteById(id); return "redirect:/"; } @GetMapping("/") public String index(HttpServletRequest request) { // 조회하기 List<Board> boardList = boardPersistRepository.findAll(); // 가방에 담기 request.setAttribute("boardList", boardList); return "index"; // 서버가 내부적으로 index를 요청 - 외부에서는 다이렉트 접근이 안됨 } @PostMapping("/board/save") public String save(BoardRequest.SaveDTO reqDTO) { // DTO 없이 구현 boardPersistRepository.save(reqDTO.toEntity()); return "redirect:/"; } @GetMapping("/board/save-form") public String saveForm() { return "board/save-form"; } @GetMapping("/board/{id}") public String detail(@PathVariable Integer id, HttpServletRequest request) { // Integer : 없으면 null, int : 0 Board board = boardPersistRepository.findById(id); request.setAttribute("board", board); return "board/detail"; } }
Share article