이번시간에는 코드를 통해 @Transactional 의 적용 위치에 따른 우선순위를 확인해보자.

 

 

인터페이스와 해당 인터페이스를 구현한 클래스에 애노테이션을 붙일 수 있다면 더 구체적인 클래스가 더 높은 우선순위를 가진다.

 

 

 

TxLevelTest

package hello.springtx.apply;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@SpringBootTest
public class TxLevelTest {


    @Autowired
    LevelService service;

    @Test
    void orderTest() {
        service.write();
        service.read();
    }

    @TestConfiguration
    static class TxLevelTestConfig {
        @Bean
        LevelService levelService() {
            return new LevelService();
        }
    }




    @Slf4j
    @Transactional(readOnly = true)
    static class LevelService {

        @Transactional(readOnly = false)
        public void write() {
            log.info("call write");
            printTxInfo();
        }

        public void read() {
            log.info("call read");
            printTxInfo();
        }

        private void printTxInfo() {
            boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
            log.info("tx active={}", txActive);
            boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
            log.info("tx readOnly", readOnly);
        }
    }

}

 

 

스프링의 @Transactional 은 다음 두 가지 규칙이 있다.

1. 우선순위 규칙
2. 클래스에 적용하면 메서드는 자동 적용

 

 

 

우선순위

트랜잭션을 사용할 때는 다양한 옵션을 사용할 수 있다. 그런데 어떤 경우에는 옵션을 주고, 어떤 경우에는 옵션을 주지 않으면 어떤 것이 선택될까? 예를 들어서 읽기 전용 트랜잭션 옵션을 사용하는 경우와 아닌 경우를 비교해보자. (읽기 전용 옵션에 대한 자세한 내용은 뒤에서 다룬다. 여기서는 적용 순서에 집중하자.)
 ● LevelService 의 타입에 @Transactional(readOnly = true) 이 붙어있다.
 ● write() : 해당 메서드에 @Transactional(readOnly = false) 이 붙어있다.
     ● 이렇게 되면 타입에 있는 @Transactional(readOnly = true) 와 해당 메서드에드 있는                                                     @Transactional(readOnly = false) 둘 중 하나를 적용해야 한다.
     ● 클래스 보다는 메서드가 더 구체적이므로 메서드에 있는 @Transactional(readOnly = false) 옵션을 사용한 트랜잭션이 적용된다.

 

 

 

클래스에 적용하면 메서드는 자동 적용

 ● read() : 해당 메서드에 @Transactional 이 없다. 이 경우 더 상위인 클래스를 확인한다.
 ● 클래스에 @Transactional(readOnly = true) 이 적용되어 있다. 따라서 트랜잭션이 적용되고 readOnly = true 옵션을 사용하게 된다.

 

 

 

참고로 readOnly=false 는 기본 옵션이기 때문에 보통 생략한다. 여기서는 이해를 돕기 위해 기본 옵션을적어주었다. 
@Transactional == @Transactional(readOnly=false) 와 같다

 

 

TransactionSynchronizationManager.isCurrentTransactionReadOnly
현재 트랜잭션에 적용된 readOnly 옵션의 값을 반환한다.

 

 

 

실행 결과

# write() 호출
TransactionInterceptor : Getting transaction for 
[..LevelService.write]
y.TxLevelTest$LevelService : call write
y.TxLevelTest$LevelService : tx active=true
y.TxLevelTest$LevelService : tx readOnly=false
TransactionInterceptor : Completing transaction for 
[..LevelService.write]

# read() 호출
TransactionInterceptor : Getting transaction for 
[..LevelService.read]
y.TxLevelTest$LevelService : call read
y.TxLevelTest$LevelService : tx active=true
y.TxLevelTest$LevelService : tx readOnly=true
TransactionInterceptor : Completing transaction for 
[..LevelService.read]

다음 결과를 확인할 수 있다.
 ● write() 에서는 tx readOnly=false : 읽기 쓰기 트랜잭션이 적용되었다. readOnly 가 아니다.
 ● read() 에서는 tx readOnly=true : 읽기 전용 트랜잭션 옵션인 readOnly 가 적용되었다.

 

 

 

인터페이스에 @Transactional 적용

인터페이스에도 @Transactional 을 적용할 수 있다. 이 경우 다음 순서로 적용된다. 구체적인 것이 더 높은 우선순위를 가진다고 생각하면 바로 이해가 될 것이다.

 

1. 클래스의 메서드 (우선순위가 가장 높다.)
2. 클래스의 타입
3. 인터페이스의 메서드
4. 인터페이스의 타입 (우선순위가 가장 낮다.)

 

클래스의 메서드를 찾고, 만약 없으면 클래스의 타입을 찾고 만약 없으면 인터페이스의 메서드를 찾고 그래도 없으면 인터페이스의 타입을 찾는다.

 

그런데 인터페이스에 @Transactional 사용하는 것은 스프링 공식 메뉴얼에서 권장하지 않는 방법이다.  AOP를 적용하는 방식에 따라서 인터페이스에 애노테이션을 두면 AOP가 적용이 되지 않는 경우도 있기 때문이다. 가급적 구체 클래스에 @Transactional 을 사용하자.

 

 

 

 

참고
스프링은 인터페이스에 @Transactional 을 사용하는 방식을 스프링 5.0에서 많은 부분 개선했다. 과거에는 구체 클래스를 기반으로 프록시를 생성하는 CGLIB 방식을 사용하면 인터페이스에 있는 @Transactional 을 인식하지 못했다. 스프링 5.0 부터는 이 부분을 개선해서 인터페이스에 있는 @Transactional 도 인식한다. 하지만 다른 AOP 방식에서 또 적용되지 않을 수 있으므로 공식 메뉴얼의 가이드대로 가급적 구체 클래스에 @Transactional 을 사용하자.

 

 

 

 

 

처 : 김영환 스프링 DB2 강의

복사했습니다!