배치 개발중에 트랜젝셔널이 제대로 작동하지 않아 원인을 찾던 중 정말 기본이 되는 부분을 놓쳐서 이를 기록하고자 글을 작성한다. 문제 해결기를 정리하며 동작 원리와 주의 사항에 대해 다시 공부하며 같이 정리하였다.
deleteStatsDatas() 메소드를 타면서 데이터 삭제가 이루어져야하는데 해당 메소드에 @Transactional이 달려야한다는 에러가 발생하였다. 그래서 붙여주었을 때는 잘 돌아가지만 사용법이 잘못되었다는 것을 알았기에 동작원리와 주의 사항을 다시 파악하고 로직을 수정하였다.
@Transactional
public FileResponse setRawDataAndStatsData(MultipartFile multipartFile) {
for(Integer date : dateList) {
makeStatsDataPerDay();
}
private void makeStatsDataPerDay() {
statsDataService.deleteStatsDatas();
}
public void deleteStatsDatas(Integer yyyymmdd) {}
@Transactional 어노테이션은 스프링에서 많이 사용되는 선언적 트랜잭션 방식이다.
해당 어노테이션은 getConnection(), setAutoCommit(false), 예외 발생 시 롤백, 정상 종료 시 커밋 등의 필요한 코드를 삽입해준다.
스프링 부트에선 @EnableTransactionManagement 설정이 되어 있어서 자동으로 사용할 수 있으며 입맛에 맞게 클래스 또는 메서드에 @Transactional 어노테이션을 적용하면 된다.
@Transactional은 Spring AOP를 통해 프록시 객체를 생성하여 사용된다.
스프링에서 Target 객체를 직접 참조하지 않고, 프록시 객체를 사용하는 이유는, Aspect 클래스에서 제공하는 부가 기능을 사용하기 위해서이다. Target 객체를 직접 참조하는 경우, 원하는 위치에서 직접 Aspect 클래스를 호출해야하기 때문에 유지보수가 어려워진다.
스프링에서 사용하는 프록시 구현체는 JDK Proxy(Dynamic Proxy), CGLib 두 가지가 있다.
RuntimeException 이나 Error 의 경우에만 실패시 롤백이 된다. Exception의 경우 rollbackFor 옵션을 주어 처리하는 방법도 있다.private 메서드는 트랜잭션의 대상이 될 수 없다.
위 문제에서 2번째 메소드 makeStatsDataPerDay()가 private 타입이므로 제일 밖에 걸어둔 @Transactional 이 걸릴 수 없는 것이 주요 문제였다.
해당 부분은 public으로 변경하였다.
이 부분을 해결하면서 delete를 JPQL에서 Querydsl로 변경하였는데 @Transactional 과 JPQL,Querydsl 연관관계에 대해 공부해보고 알게되면 정리를 또 해보겠다.
Reference.