Spring @Transactional 주의점

안녕하세요. 오늘은 Spring@Transactional 어노테이션을 사용할 때, 주의점에 대해 포스팅합니다.

 

Spring 에서 JPA 기술을 쓸 때 빼놓을 수 없는 기능중 하나는 @Transactional 입니다.

아래와 같이 많은 편리성을 우리에게 제공해주기 때문이지요.

  • transaction begin, commit을 자동 수행해준다.
  • 예외를 발생시키면, rollback 처리를 자동 수행해준다.

 

그렇기에, 많은 보일러코드를 줄 일 수 있기 때문에, 아래와 같이 편리하게 코드를 작성할 수 있습니다.

Note. 문제 상황을 억지로 만들기 위해 코드 효율성이 좋지 않을 수 있습니다.

 

문제는 예시에서 쓴 addBook 메서드를 Books 클래스 내부에서 사용할 때 발생 할 수 있습니다.

 

혹시, addBooks 메서드를 사용할 때, 어떤것이 문제가 될 수 있는지 예상이 가시나요?

문제는 addBooks 메서드 내부에서 호출하는 addBook 메서드의 @Transactional 어노테이션이 적용되지 않는 것입니다.

 

그렇기에, 해당 코드를 실행하더라도 Database에는 저장된 Book 정보에 Flag 컬럼에 정상적으로 업데이트 되지 않습니다.

bookRepositorysave 메서드는 자체적으로 @Transactional 어노테이션이 붙어있어, 정상적으로 코드가 수행이 되어 Database에 insert 를 수행합니다.

하지만, update 의 역할을 하는 book 객체의 변경감지가 동작하지 않아, Flag 컬럼에 업데이트가 되지 않습니다.

 

@Transactional 이 동작하지 않았는지는 Spring@Transactional 기능을 제공하는 방식에 대해 살펴볼 필요가 있습니다.

 

Spring @Transactonal 기능제공 방식

JPA 의 객체 변경감지는 transactoncommit 될 때, 작동합니다.

그렇기에 Spring@Transactonal 어노테이션을 선언한 메서드가 실행되기전, transaction begin 코드를 삽입하며

메서드가 실행된 후, transaction commit 코드를 삽입하여, 객체 변경감지를 수행하게 유도합니다.

 

Spring 의 코드 삽입 방법은 크게 2가지 방법이 있습니다.

  • 바이트 코드 생성 (CGLIB 사용)
  • 프록시 객체 사용

2가지 방법중 Spring 은 기본적으로 프록시 객체 사용 이 선택됩니다. 그렇기에 interface 가 반드시 필요합니다.

SpringBoot 는 기본적으로 바이트 코드 생성 이 선택됩니다. 그렇기에, 굳이 interface 가 필요없습니다.

만약 개발환경이 SpringBoot 라면 Books 인터페이스 없이 봐도 무방합니다.

 

원리는 이렇습니다. 프록시 객체로 우리가 만든 메서드를 한번 감싸서, 메서드 위 아래로 코드를 삽입 해줍니다.

 

그렇기에, 우리는 BooksImpl 자료형을 사용 할 때, 스프링이 제공하는 BooksProxy 객체를 사용하게 되며,

BooksProxy 객체가 제공하는 addBook 메서드를 사용해야만 transaction 처리가 수행되는것입니다.

 

@Transactonal 이 수행되지 않은 이유.

다시 아래의 예제를 살펴봅니다.

 

BooksProxyaddBooks 메서드를 수행하면, 아래와 같은 순서로 작동됩니다.

BooksProxy::addBooks -> BooksImpl::Book

 

즉, BooksImpl 내부의 코드(addBook) 가 수행 되기 때문에 해당 메서드는 프록시로 감싸진 메서드가 아니라는 점에서

@Transactonal 어노테이션 기능이 수행되지 않는다는 것입니다.

 

해결방법

사실 @Transactional 메서드를 내부적으로 사용하지 않는것이 근본적인 해결책입니다.

하지만 만약 굳이 사용해야 겠다면, 의존성 주입을 이용하여 Proxy 인스턴스를 자체적으로 가져와 사용하는 방법이 있습니다.

 

해당 코드는 Books 인터페이스를 이용하여 BooksProxy 인스턴스를 주입할 수 있도록 유도합니다.

생성자 주입을 사용하면, 순환 에러가 발생합니다. @Autowired 로만 해주세요. Autowired 싫다.

 

그 후, 자기 자신 즉 this 가 가지고 있는 순수한 addBook 메서드가 아니라 proxy 로 감싸진 addBook 메서드를 통해@Transactional 어노테이션 기능을 사용할 수 있게 됩니다.

 

정리하자면

  • @Transactonal 어노테이션 메서드는 클래스 내부적으로 사용하지 말고, 밖에서 사용하자. (프록시 객체 때문에)
  • 굳이 내부적으로 사용하려면, 자기 자신의 Proxy 객체를 사용하여 처리하자.

 

마무으리

혹시 틀린 내용이 있어서, 알려주신다면 감사하게 듣겠습니다. (_ _)

긴글 읽어주셔서 감사합니다!

 

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

+ Recent posts