-
[Spring] 트랜잭션에 대한 실험과 고찰 (Self-invocation, 프록시가 메서드를 실행하기까지의 과정)Back End/Spring Framework 2024. 12. 1. 21:36
Spring DB 트랜잭션의 롤백 유무에 대해 정리하다가 프록시 객체가 언제 생성되는지, 어떤 원리로 만들어지는지 고찰을 하기 위해 여러 실험을 진행해보았다. 다른 블로그 글을 읽어도 이해가 잘안되고, 블로그마다 말이 좀 다른게 있어 쉽게 믿을 수가 없었다.
무엇보다 제일 궁금했던 점은 프록시가 나의 서비스클래스를 상속받아서 만들어지는지, 아니면 주입받아서 사용하는지의 여부였다.
경제적인 관점에서 주입받아서 사용하는 편이 비용적으로 저렴하니까 당연하다 생각하였는데, 다른 블로그 포스팅에서 상속받아서 만들어낸다는 글도 있어서 무시할 수가 없었다.
검증 전 필자가 생각한 이상적인 프록시 객체의 코드
@Class class ProxyService( private val myUserService: MyUserService, ): Proxy { fun registerUser(...) { Proxy.preProcess() myUserService.registerUser(...) Proxy.postProcess() } }
요롷게 안되어있으면 컨트리뷰트 해봐야지!!! +_+
아래 내용들은 스프링 라이브러리 코드를 기반으로 디버깅하여 모두 확인 검증하였으니 안전하게(?) 받아들여도 된다.
검증 방법 따라해보기
더보기1. CglibAopProxy (프록시 생성 과정 및 원객체 메서드 실행 디버깅 가능)
2. TransactionInterceptor (트랜잭션 인터셉터 실행과정 디버깅 가능)
3. logging.level.org.springframework.transaction.interceptor: TRACE
(트랜잭션 열리고 닫히는 과정을 로깅을 통해 관찰가능)4. TransactionInterceptor.invoke() -> CglibAopProxy.proceed() 의 this.target 의 주소값과 서비스 메서드가 실행될때의 서비스 주소값을 비교
@Transactional 어노테이션으로 인해 생성된 프록시가 메서드를 실행하기 까지의 과정
// <When Application Initialized> 1. Parsing the Annotation 2. Proxy Creation // <When Proxy(service) method called> 3. Transaction Advice Execution 4. Method Execution (해당 메서드는 프록시 객체가 아닌 서비스 객체의 메서드이다.) 5. Transaction Commit
여기서 주목할 점은 4. Method Excution 이 프록시 객체가 아닌 스프링빈의 메서드라는 점이다.
즉, 프록시는 우리가 작성한 서비스를 상속하여 생성되는 것이 아니라 주입 받아서 생성되는 것이다.
이 점을 검증하기 위해서 정말 고생을 많이했다...
스프링빈의 메서드라는 점 하나로 많은게 당연시 된다.
1. private 메서드에는 @Transactional 을 붙여도 동작하지 않는다. 2. Self-invocation 시, 하위 메서드들에 붙은 @Transcational 은 무시된다. 3. Self-invocation 시, 하위 메서드들은 상위 메서드의 트랜잭션에 종속된다.
따라서 상위 메서드의 트랜잭션과 하위 메서드의 트랜잭션을 분리하여 처리하고 싶다면, 하위 메서드가 프록시객체에서 호출되어야 한다.
이를 실현하기 위해 아래의 방법을 사용할 수 있다.
1. 하위 메서드와 상위 메서드의 클래스를 분리시킨다. 2. 순환참조로 자기자신을 주입받은 후, 순환참조된 객체에서 하위메서드를 호출한다. (권장하지 않습니다)
2번은 권장하지 않는데, 단일책임의 원칙으로 설계된 클래스의 트랜잭션이 서로 분리되어 처리되어야 할 일은 거의 없을 것이기 때문이다.
따라서 2번의 경우가 발생한다면 클래스의 책임분리가 잘되어있는지 고민해야하는 시점이라고 생각한다.
+ 트랜잭션 롤백유무에 관한 실험과 결과
Self-invocation
상위 메서드
@Transactional상위 메서드 예외 하위 메서드
@Transactional하위 메서드 예외 롤백유무 (상위 / 하위) O X O O(Runtime) O / O O X O(rollbackFor) O(Checked) X / X O(rollbackFor) X O or X O(Checked) O / O O O(Runtime) O(requires_new) X O / O Non-self-invocation
상위 메서드
@Transactional상위 메서드 예외 하위 메서드
@Transactional하위 메서드 예외 롤백유무 (상위 / 하위) O X O O(Runtime) O / O O X O(rollbackFor) O(Checked) O / O O(rollbackFor) X O or X O(Checked) O / O O O(Runtime) O(requires_new) X O / X O X O(requires_new) O(Runtime) O / O O X O(requires_new,
rollbackFor)O(Checked) X / O
+ 컨트리뷰트 할 거리 없나 싶어 추가로 실험/검증한 사실
1. @Transactional 어노테이션을 가지지 않은 서비스빈들은 프록시 객체로 생성되지 않고 스프링빈으로 생성 됨. 2. @Transactional 어노테이션이 없더라도, 순환참조로 자기자신을 참조할 경우 프록시 객체가 생성 됨. 3. 트랜잭션이 분리가 되더라도 Runtime Exception 이 상위 메서드에 전파가 되고 상위 메서드에서 따로 핸들링하지 않는다면 상위 메서드의 트랜잭션도 롤백된다.
스프링에 컨트리뷰트 해보고 싶었는데 역시 이미 잘 만들어져있다 ㅎㅎㅎ... 쳇...
728x90'Back End > Spring Framework' 카테고리의 다른 글
[Spring] 예외 발생시 Spring DB 트랜잭션 롤백 안되는 경우 (1) 2024.11.27 [Spring Framework] QueryDsl 적용하기 (with Maven, IntelliJ) - 소스코드에 generated 폴더 없이 사용하기 (0) 2022.08.28 [Spring Framework] 다국어 처리 설정 (message properties) (0) 2022.08.15 [Spring Framework] Maven 라이브러리 연동(ClassNotFoundException: javax.servlet.jsp.jstl.core.Config) (1) 2022.06.09 [Spring] Controller 에서 가변 json body 받기 (0) 2022.05.21