스프링에서 우선순위는 항상 더 구체적이고 자세한 것이 높은 우선순위를 가진다. 이것만 기억하면 스프링에서 발생하는 대부분의 우선순위를 쉽게 기억할 수 있다. 그리고 더 구체적인 것이 더 높은 우선순위를 가지는 것은 상식적으로 자연스럽다
예를 들어서 메서드와 클래스에 애노테이션을 붙일 수 있다면 더 구체적인 메서드가 더 높은 우선순위를 가진다.
인터페이스와 해당 인터페이스를 구현한 클래스에 애노테이션을 붙일 수 있다면 더 구체적인 클래스가 더 높은 우선순위를 가진다
@SpringBootTest
public class TxLevelTest {
@Autowired LevelService service;
@Test
public void orderTest() {
service.write();
service.read();
}
@TestConfiguration
static class TxLevelTestConfig{
@Bean
public LevelService levelService(){
return new LevelService();
}
}
@Slf4j
@Transactional(readOnly = true)
static class LevelService{
@Transactional
public void write(){
log.info("call write");
printTxInfo();
}
public void read(){
log.info("call read");
printTxInfo();
}
public void printTxInfo(){
boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("tx active={}", txActive);
boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
log.info("tx readOnly={}", readOnly);
}
}
}
우선순위
트랜잭션을 사용할 때는 다양한 옵션을 사용할 수 있다. 그런데 어떤 경우에는 옵션을 주고, 어떤 경우에는 옵션을 주지 않으면 어떤 것이 선택될까?
예를 들어서 읽기 전용 트랜잭션 옵션을 사용하는 경우와 아닌 경우를 비교해보자. (읽기 전용 옵션에 대한 자세한 내용은 뒤에서 다룬다. 여기서는 적용 순서에 집중하자.)
- 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는 기본 옵션이기 때문에 보통 생략한다
- TransactionSynchronizationManager.isCurrentTransactionReadOnly
- 현재 트랜잭션에 적용된 readOnly 옵션의 값을 반환한다.
인터페이스에 @Transactional 적용
- 클래스의 메서드 (우선순위가 가장 높다.)
- 클래스의 타입
- 인터페이스의 메서드
- 인터페이스의 타입 (우선순위가 가장 낮다.)
클래스의 메서드를 찾고, 만약 없으면 클래스의 타입을 찾고 만약 없으면 인터페이스의 메서드를 찾고 그래도 없으면 인터페이스의 타입을 찾는다.
그런데 인터페이스에 @Transactional 사용하는 것은 스프링 공식 매뉴얼에서 권장하지 않는 방법이다. AOP를 적용하는 방식에 따라서 인터페이스에 애노테이션을 두면 AOP가 적용이 되지 않는 경우도 있기 때문이다. 가급적 구체 클래스에 @Transactional을 사용하자
참고
스프링은 인터페이스에 @Transactional 을 사용하는 방식을 스프링 5.0에서 많은 부분 개선했다. 과거에는 구체 클래스를 기반으로 프록시를 생성하는 CGLIB 방식을 사용하면 인터페이스에 있는 @Transactional을 인식하지 못했다. 스프링 5.0부터는 이 부분을 개선해서 인터페이스에 있는 @Transactional 도 인식한다. 하지만 다른 AOP 방식에서 또 적용되지 않을 수 있으므로 공식 매뉴얼의 가이드대로 가급적 구체 클래스에 @Transactional을 사용하자.
'스프링 DB 2편(데이터 접근 활용 기술)' 카테고리의 다른 글
| Ch09. 스프링 트랜잭션 이해 - 트랜잭션 AOP 주의 사항(초기화 시점) (0) | 2022.07.06 |
|---|---|
| Ch09. 스프링 트랜잭션 이해 - 트랜잭션 AOP 주의 사항(프록시 내부 호출) (0) | 2022.07.06 |
| Ch09. 스프링 트랜잭션 이해 - 프로젝트 생성 & 트랜잭션 적용 확인 (0) | 2022.07.06 |
| Ch09. 스프링 트랜잭션 이해 - 스프링 트랜잭션 소개 (0) | 2022.07.06 |
| Ch08. 활용 방안 - 다양한 데이터 접근 기술 조합 (0) | 2022.07.04 |