본문 바로가기

Spring

[Spring] @Transactional

Transaction

사전적 정의로 "거래"를 뜻하는데요. Transaction 하면 나오는 대표적인 개념이 ACID입니다.

 

  • ACID
    • Atomicity : 하나의 Transaction 내에서 실행한 작업들은 하나의 단위로 처리
    • Consistency : Transaction은 일관성 있는 데이터베이스 상태를 유지
    • Isolation : 동시에 실행되는 Transation들이 서로 영향을 미치지 않도록 격리
    • Durability : 트랜잭션을 성공적으로 마치면 결과가 항상 저장

 따라서 Transaction이란 위 조건들을 모두 만족하는 하나의 단위를 생각하면 될 것 같습니다.

 

@Transactional

Spring에서는 Transaction 설정을 애노테이션 기반으로 사용할 수 있습니다. 이를 선언적 트랜잭션이라합니다.

 

  • @Transactional
    • 메소드 또는 클래스에 명시할 수 있는데, 둘다 명시하면 메소드가 우선 적용됩니다.
    • 하나라도 실패 할 경우 전체 작업을 취소합니다.
    • 동작은 AOP를 통해 "명시대상이 상속하고 있는 인터페이스" 또는 "명시대상 객체를 상속한" Proxy 객체가 생성되며, Proxy 객체의 메소드를 호출하면 명시대상 메소드 전 후로 트랜잭션 처리를 수행합니다. 

Spring AOP Process

JDK Dynamic Proxy

AOP Proxy 생성과정에서 TargetObjectImpl하나 이상의 인터페이스를 구현하고 있다면 JDK Dynamic Proxy를 사용합니다. 타깃이 상속하고 있는 인터페이스를 상속해 Proxy 객체를 만들고 내부적으로 타깃 메소드 호출 전 후로 트랜잭션 처리를 합니다. 

여기서 타깃은 호출하는 실제 객체를 의미합니다. 이 실제 객체가 상속하고있는 인터페이스가 있어야만 JDK Dynamic Proxy를 사용할 수 있는데, 그 이유는 Proxy.newProxyInstance 를 사용해 proxy 객체를 구현하는데, 이때 proxy를 인터페이스 기반으로 구현하기 때문입니다. 

추가적으로 해당 proxy가 수행할 로직은 InvocationHandler에 적용하여 Proxy.newInsatance에 던지는 식으로 구성되어있습니다. InvocationHandler 는 proxy가 수행할 로직을 담는 곳이라고만 인식할 수 있지만, 이는 그냥 로직이 아닌 공통 로직을 처리하는 곳이라고 재정의 할 수 있습니다.

Java Reflection 패키지의 Proxy 클래스가 개발자를 대신해 동적으로 생성해주므로 Dynamic이라는 말이 붙었습니다.

 

 

CGLib Proxy

AOP Proxy 생성과정에서 TargetObject 하나 이상의 인터페이스를 구현하고 있지 않다면 CGLIB를 사용합니다.

타깃에 대해 Proxy 객체를 만들고 내부적으로 타깃 메소드 호출 전 후로 트랜잭션 처리를 합니다.

Spring Boot는 기본값으로 CGLib Proxy를 사용합니다.

 

해당 링크에서 제가 강의를 보며 수동으로 구현한 JDK Dynamic 프록시, CGLIB를 이용한 프록시를 더 자세히 코드로 볼 수 있습니다.

 

[Design Pattern] Proxy Pattern, Decorator Pattern

* 해당 블로그의 모든 내용과 사진은 아래 참고자료를 작성하였습니다. 아래 글에 이어서 강의에서 배운 내용을 토대로 Proxy Pattern을 정리해보겠습니다. [Design Pattern] 프록시 패턴과 이터레이터

mirr-coding.tistory.com

 

Transaction 경계

트랜잭션에는 경계를 설정할 수 있는데 주로 사용하는 방법은 선언적 트랜잭션입니다. 선언적트랜잭션이 경계를 설정할 수 있는 이유는 위에서 설명드린 설명한 프록시 객체 덕분입니다. 

또한 Spring AOP는 트랜잭션을 메소드 단위로 관리합니다. 메소드가 끝날 때까지 커밋 또는 커넥션 반환이 이루어지지 않으므로 처리시간이 긴 메소드의 경우 트랜잭션 단위를 조정해서 DB Lock 지속 시간이 길어지거나 DB 커넥션 풀의 커넥션 개수가 모자라지 않도록 조절해야합니다.

Transaction 전파

트랜잭션의 동작방식을 결정하는 설정입니다. 

REQUIRED(기본값)

이미 진행중인 트랜잭션이 없으면 새로 시작하고, 있다면 기존 트랜잭션에 참여합니다.

A라는 트랜잭션이 진행중이라면 B는 따로 트랜잭션을 생성하지 않고 A에 합류합니다.

따라서 해당 로직은 A 트랜잭션만 존재합니다.

REQUIRED_NEW

항상 새로운 트랜잭션을 시작하는 방식입니다.

존재하는 트랜잭션 유무와 상관없이 새로운 트랜잭션을 만들어 독립적으로 동작시킵니다.

독립적인 트랜잭션이 보장되어야 한다면 쓰입니다.

MANDATORY

이미 진행중인 트랜잭션이 있다면 합류하지만 그렇지않다면 예외를 발생시킵니다.

독립적인 트랜잭션을 실행하면 안되는 경우에 사용합니다.

NESTED

이미 진행중인 트랜잭션이 있다면 중첩 트랜잭션을 만듭니다.

중첩 트랜잭션이란 트랜잭션 내부에 다시 트랜잭션을 만드는 것입니다. 

중첩 트랜잭션은 부모 트랜잭션의 커밋에는 영향을 받지만, 중첩 트랜잭션 본인은 부모 트랜잭션에 영향을 주지 않습니다.

예를들어 부모 트랜잭션이 끝난 후 롤백이 발생한다면 모든 트랜잭션을 롤백합니다.

또한 중첩 트랜잭션이 끝나도 부모 트랜잭션이 끝나야 모든 커밋이 이루어집니다.

 

문제상황

Q1 : Spring Bean의 메소드 A@Transactional을 적용하였고, 해당 Bean의 메소드 B가 호출되었을 때, B 메소드 내부에서 A 메소드를 호출하면 Transaction이 적용된 코드가 수행될까요?

 

A1 : 먼저 Proxy 객체가 동작하는지 파악해야될 것입니다. Proxy 객체는 명시대상 객체를 호출하는 과정에서만 동작하므로, A 메소드는 프록시로 감싸진 코드가 아닌 일반적인 코드가 동작합니다.

 

 

Q2 : A 라는 Service 객체의 메소드가 존재하고, 그 메소드 내부에서 트랜잭션 3개가 존재한다고 할 때, @Transactional을 A 메소드에 적용하면 어떻게 될까요?

 

A2 : 트랜잭션 전파 수준에 따라 다른결과를 내는데, 기본값인 REQUIRED라면 트랜잭션 3개가 모두 부모 트랜잭션인 A에 합류하여 수행합니다. 따라서 부모 트랜잭션이나 자식 트랜잭션 3개가 모두 같은 트랜잭션이 되고 어느 하나의 로직에 문제가 발생하면 모두 롤백됩니다.

 

 

Q3 : @TransactionalreadOnly 속성을 사용하는 이유는?

 

A3 : 트랜잭션 안에서 수정/삭제 작업을 안할때 주로 사용합니다. 영속성 컨텍스트에서 readOnly 속성을 사용한다면 엔티티를 관리할 필요가 없어지기 때문에 메모리 성능을 높일 수 있습니다. 

 


● 참고자료 : https://steady-coding.tistory.com/610

'Spring' 카테고리의 다른 글

[Spring] Bean  (0) 2023.02.17
[Spring] Spring MVC  (0) 2023.02.16
[Spring] Redis  (0) 2023.02.11
[Spring] WS/WAS  (2) 2023.01.15
[Spring] IOC-DI  (0) 2023.01.15