[Spring] @Transactional 동작원리 및 사용 시 주의 사항 (feat. 해결기)

yeonjoo913·2023년 12월 26일
0

Spring

목록 보기
19/19

배치 개발중에 트랜젝셔널이 제대로 작동하지 않아 원인을 찾던 중 정말 기본이 되는 부분을 놓쳐서 이를 기록하고자 글을 작성한다. 문제 해결기를 정리하며 동작 원리와 주의 사항에 대해 다시 공부하며 같이 정리하였다.

문제

deleteStatsDatas() 메소드를 타면서 데이터 삭제가 이루어져야하는데 해당 메소드에 @Transactional이 달려야한다는 에러가 발생하였다. 그래서 붙여주었을 때는 잘 돌아가지만 사용법이 잘못되었다는 것을 알았기에 동작원리와 주의 사항을 다시 파악하고 로직을 수정하였다.

@Transactional
public FileResponse setRawDataAndStatsData(MultipartFile multipartFile) {
		for(Integer date : dateList) {
			makeStatsDataPerDay();
}

private void makeStatsDataPerDay() {
		statsDataService.deleteStatsDatas();
}

public void deleteStatsDatas(Integer yyyymmdd) {}

@Transactional이란?

@Transactional 어노테이션은 스프링에서 많이 사용되는 선언적 트랜잭션 방식이다.
해당 어노테이션은 getConnection(), setAutoCommit(false), 예외 발생 시 롤백, 정상 종료 시 커밋 등의 필요한 코드를 삽입해준다.

@Transactional 사용 방법

스프링 부트에선 @EnableTransactionManagement 설정이 되어 있어서 자동으로 사용할 수 있으며 입맛에 맞게 클래스 또는 메서드에 @Transactional 어노테이션을 적용하면 된다.

  • PlatformTransactionManager는 스프링이 제공하는 TransactionManager의 최상위 인터페이스로, 환경에 맞는 클래스를 주입할 수 있도록 구성되어 있다. DataSourceTransactionManger, JpaTransactionManager 등 필요한 정보를 Bean으로 등록하고 DI를 받아 사용한다.
  • 스프링 컨테이너는 @Transactional 어노테이션이 있으면, 해당 타겟 빈을 상속받은 프록시 객체를 생성한다. 따라서 private 메서드는 상속이 불가하기 때문에 어노테이션을 붙여도 동작하지 않는다.

@Transactional 동작 원리

@Transactional은 Spring AOP를 통해 프록시 객체를 생성하여 사용된다.

스프링에서 Target 객체를 직접 참조하지 않고, 프록시 객체를 사용하는 이유는, Aspect 클래스에서 제공하는 부가 기능을 사용하기 위해서이다. Target 객체를 직접 참조하는 경우, 원하는 위치에서 직접 Aspect 클래스를 호출해야하기 때문에 유지보수가 어려워진다.

스프링에서 사용하는 프록시 구현체는 JDK Proxy(Dynamic Proxy), CGLib 두 가지가 있다.

  • JDK Dynamic Proxy : Target 클래스가 인터페이스 구현체일 경우 생성되며, 구현 클래스가 아닌 인터페이스를 프록시 객체로 구현해서 코드에 끼워넣는 방식이다.
  • CGLib Proxy : 스프링에서 사용하는 디폴트 프록시 생성방식으로, Target 클래스를 프록시 객체로 생성하여 코드에 끼워넣는 방식이다.

@Transactional 사용 시 주의 사항

  • private 메서드는 트랜잭션의 대상이 될 수 없다.
  • 스프링에서 트랜잭션은 처음으로 호출하는 메서드나 클래스의 속성을 따라가게 되어있다. 따라서 동일한 빈 안에서 하위 메서드에서만 트랜잭션이 설정되어 있다면, 전이되지 않는다. (반대로, 상위에 적용되면 하위 메서드는 트랜잭션 설정이 없어도 전이가 된다.) 따라서 클래스를 분리하거나, 상위 메서드에 트랜잭션을 설정해야한다.
  • RuntimeException 이나 Error 의 경우에만 실패시 롤백이 된다. Exception의 경우 rollbackFor 옵션을 주어 처리하는 방법도 있다.

해결기

private 메서드는 트랜잭션의 대상이 될 수 없다.

위 문제에서 2번째 메소드 makeStatsDataPerDay()가 private 타입이므로 제일 밖에 걸어둔 @Transactional 이 걸릴 수 없는 것이 주요 문제였다.

해당 부분은 public으로 변경하였다.

이 부분을 해결하면서 delete를 JPQL에서 Querydsl로 변경하였는데 @Transactional 과 JPQL,Querydsl 연관관계에 대해 공부해보고 알게되면 정리를 또 해보겠다.


Reference.

https://sasca37.tistory.com/267

profile
주니어 백엔드 개발자. 까먹는다 기록하자!

0개의 댓글