@Transactional 어노테이션에 대한 정리
@Transactional 어노테이션 사용 시 사용자가 지정한 rollback rules이 없을 경우 RuntimeException, Error일 때 롤백되고 checked Exception에선 롤백되지 않는다.
상황 : 같은 Class 내 오류가 발생해도 서로 다른 propagation을 설정해 부모 트랜잭션에서 오류가 발생해도 자식 트랜잭션은 커밋되게 동작시키려함
예상한 동작 방식 : saveContent내부의 save는 동작하지 않아도 updateContent내부의 save는 동작할 것으로 예상함.
이유 : require_new로 트랜잭션을 신규로 생성했고 부모에서 exception이 발생해도 자식은 트랜잭션이 신규로 생성되기 때문에 save될 것이라 예상함
@Transactional(rollbackFor = Exception.class)
public void saveContent(BoardDTO boardDTO) throws Exception {
Board board = new Board(boardDTO);
boardRepository.save(board);
updateContent(1000L, boardDTO);
throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void updateContent(Long contentId, BoardDTO boardDTO) {
Board board = boardRepository.findById(contentId).orElseThrow();
board.updateContent(boardDTO.getTitle(), boardDTO.getContent());
boardRepository.save(board);
}
위의 예제 코드로 테스트를 하였으나 내가 기대한 방식으로 동작하지 않았고 원인은 Spring에서 @Transactional 어노테이션에 대한 동작 방식 때문이었다. @Transaction 어노테이션은 Spring의 AOP를 통해 프록시 객체를 생성하여 사용된다.
Target 클래스가 인터페이스 구현체가 아니어서 테스트 코드상에서는 CGLib Proxy 방식으로 Target 클래스를 프록시 객체로 생성하여 코드에 끼워넣는 방식이 사용됐다.
문제는 Target 클래스를 프록시 객체로 생성의 특징으로 인해 발생했는데 동일한 Class내에 위치한 메서드에 각기 다른 @Transactional propagation 옵션을 설정했지만 메소드 단위가 아닌 Target클래스 단위로 설정되기 때문에 새로운 트랜잭션이 생성되지 않는다.
이를 해결하기 위해선 메소드를 다른 클래스로 분리(bean을 분리해줌)시키면 외부에서 호출하기 때문에 Target클래스 단위로 트랜잭션이 생성될 수 있고(propagation 전파 옵션에 따라) 부모가 실패해도 자식 트랜잭션은 커밋될 수 있도록 설정할 수 있다.
'IT 개발 > 개념 정리' 카테고리의 다른 글
kotlin에서 queryDSL 사용하기 (1) | 2024.07.05 |
---|---|
ByteArray -> human readable string (0) | 2024.04.29 |
Spring Security 환경에서 h2 console enabled : true일 때 오류 (0) | 2023.09.10 |
[아이템 61] 박싱된 기본 타입보다는 기본 타입을 사용하라 (0) | 2022.08.15 |
[아이템 54] null이 아닌, 빈 컬렉션이나 배열을 반환하라 (0) | 2022.07.29 |