
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