The Office Lover
Java Spring Framework - @Transactional 본문
개요
@Transactional은 스프링 프레임워크에서 트랜잭션 관리를 선언적으로 처리하는 데 사용되는 어노테이션입니다. 이를 사용하여 트랜잭션의 시작과 종료, 속성 설정, 롤백 처리 등을 자동화할 수 있습니다.
적용 범위
- 클래스
- 인터페이스
- 메서드
해당 범위에서 적용 가능하며, 어노테이션이 붙은 범위 내에서 트랜잭션이 관리됩니다.
적용 범위에 따른 우선순위
1. 메서드(Method) > 클래스(Class) > 인터페이스(Interface) > 빈(Bean)
- 메서드 단위에 @Transactional 어노테이션이 적용된 경우, 해당 메서드의 트랜잭션 설정이 우선적으로 적용됩니다.
- 클래스 단위에 적용된 경우 해당 클래스의 모든 public 메서드에 영향을 미치지만, 메서드 단위에서 재정의 된 설정이 있다면 메서드 단위 설정이 우선 적용됩니다.
- 인터페이스, 빈 단위에 적용 또한 위 내용과 동일합니다.
2. 메서드 단위에 중복 적용되었을 경우
- 하나의 메서드에 여러 개의 @Transactional 어노테이션이 적용된 경우, 가장 트랜잭션 범위가 좁은 설정이 우선 적용됩니다. 즉, 가장 안쪽에 있는 트랜잭션이 설정 적용됩니다.
메서드 단위의 트랜잭션 설정이 우선순위가 높기 때문에 가능한 메서드 단위로 @Transactional 어노테이션을 적용하는 것을 권장합니다. 하지만, 필요에 따라서는 클래스나 인터페이스 단위로도 적용하는 것이 일반적입니다. 빈 단위로 적용하는 경우는 특별한 상황에서만 사용되며, 주로 특정 빈의 모든 메서드가 동일한 트랜잭션 속성을 가져야 할 때 사용될 수 있습니다. 하지만, 이 경우에도 메서드 단위로 세밀하게 설정하는 것이 더 권장됩니다.
주요 속성
- readOnly : 해당 트랜잭션이 읽기 전용인지 여부를 설정합니다.
- true인 경우 Hibernate와 같은 ORM 프레임워크는 성능을 최적화하는 데 도움이 될 수 있는 여러 가지 내부 최적화를 수행할 수 있습니다.
// default = false
@Transactional(readOnly = true)
public void somBusinessLogic() {...}
@Transactional(readOnly = false)
public void somBusinessLogic() {...}
- timeout : 트랜잭션의 타임아웃 시간을 설정합니다.
- 설정한 시간을 초과하면, 트랜잭션 시스템은 롤백하고 'TransactionTimeOutException'을 반환합니다.
// default = -1
@Transactional(timeout = 20)
public void somBusinessLogic() {...}
- rollbackFor / noRollbackFor : 특정 예외 발생 시 트랜잭션 롤백 여부를 설정합니다.
- rollbackFor 속성은 트랜잭션 내에서 특정 예외가 발생했을 때 트랜잭션을 롤백하도록 설정하는 것입니다. 클래스 또는 클래스 이름의 배열을 인수로 받을 수 있습니다.
- noRollbackFor 속성은 특정 예외가 발생했을 때 트랜잭션을 롤백하지 않도록 설정합니다.
@Transactional(rollbackFor = IOException.class)
public void somBusinessLogic() {...}
@Transactional(noRollbackFor = FileNotFoundException.class)
public void somBusinessLogic() {...}
- propagaion : 트랜잭션의 전파 방식을 설정합니다.
- 가장 일반적인 값으로는 'Propagation.REQUIRED'와 'Propagation.REQUIRES_NEW'가 있습니다.
- REQUIRED : 부모 트랜잭션 내에서 실행되거나, 부모 트랜잭션이 없는 경우 새로운 트랜잭션을 시작합니다.
- SUPPORTS : 이미 시작된 트랜잭션이 있으면 그것을 사용하고, 없으면 트랜잭션 없이 실행됩니다.
- MANDATORY : 이미 시작된 트랜잭션이 있으면 그것을 사용하고, 없으면 예외를 던집니다.
- REQUIRES_NEW : 항상 새 트랜잭션을 시작하며, 이미 진행 중인 트랜잭션이 있으면 잠시 보류(suspend) 상태로 만듭니다.
- NOT_SUPPORTED : 트랜잭션 없이 메서드를 실행하며, 이미 진행 중인 트랜잭션이 있으면 잠시 보류(suspend) 상태로 만듭니다.
- NEVER : 트랜잭션 없이 메서드를 실행하며, 이미 진행 중인 트랜잭션이 있으면 예외를 던집니다.
- NESTED : 부모 트랜잭션 내에서 중첩 트랜잭션을 시작하거나, 부모 트랜잭션이 없는 경우 새로운 트랜잭션을 시작합니다. 이 옵션은 JDBC 3.0 드라이버에서 지원하는 'Savepoint' 기능을 사용합니다.
// defalt = REQUIRED
@Transactional(propagation = Propagation.MANDATORY)
public void somBusinessLogic() {...}
@Transactional(propagation = Propagation.NESTED)
public void somBusinessLogic() {...}
@Transactional(propagation = Propagation.NEVER)
public void somBusinessLogic() {...}
@Transactional(propagation = Propagation.REQUIRED)
public void somBusinessLogic() {...}
@Transactional(propagation = Propagation.SUPPORTS)
public void somBusinessLogic() {...}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void somBusinessLogic() {...}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void somBusinessLogic() {...}
- isolation : 트랜잭션의 격리 수준을 설정하는데 사용합니다.
- 트랜잭션 격리 수준은 한 트랜잭션이 다른 트랜잭션에게 보이는 데이터를 결정합니다.
- READ_UNCOMMITTED (읽기 미확정) : 한 트랜잭션이 다른 트랜잭션의 변경된, 하지만 아직 커밋되지 않은 데이터를 읽을 수 있습니다. 이렇게 되면 Dirty Read, Non-Repeatable Read, Phantom Read라는 세 가지 문제가 발생할 수 있습니다.
- READ_COMMITTED (읽기 확정) : 한 트랜잭션이 다른 트랜잭션의 변경된 데이터를 읽을 수 있지만, 해당 변경이 커밋된 후에만 가능합니다. 이 수준은 Dirty Read 문제를 해결하지만, Non-Repeatable Read, Phantom Read 문제는 여전히 발생할 수 있습니다.
- REPEATABLE_READ (반복 가능한 읽기) : 한 트랜잭션이 다른 트랜잭션에 의해 변경된 데이터를 읽지 못하도록 합니다. 즉, 트랜잭션 도중에 다른 트랜잭션에서 변경된 데이터를 반복해서 읽어도 동일한 결과를 보장합니다. 이 수준은 Dirty Read와 Non-Repeatable Read 문제를 해결하지만 Phantom Read 문제는 여전히 발생할 수 있습니다.
- SERIALIZABLE (직렬 가능) : 트랜잭션들이 순차적으로 실행되어야 하므로 동시성을 완전히 제거합니다. 이 수준은 Dirty Read, Non-Repeatable Read, Phantom Read 모든 문제를 해결하지반, 트랜잭션 동시성이 제거되므로 성능이 크게 떨어질 수 있습니다.
- DEFAULT : 특정 격리 수준을 지정하지 않을 때 사용하는 격리 수준입니다. 이 옵션은 Spring은 백엔드 DBMS의 기본 트랜잭션 격리 수준을 사용합니다. 그래서, 'Isolation.DEFAULT'가 실제로 어떤 격리 수준을 나타내는지는 사용하는 DBMS에 따라 달라집니다.
예를 들면, MySQL의 경우 기본 격리 수준은 'REPEATABLE_READ'이며, ORCLE의 경우 격리 수준은 'READ_COMMITTED', PostgreSQL의 기본 격리 수준은 'READ_COMMITTED'입니다.
해당 격리 설정으로 사용 시 문제 될 수 있는 부분은 다음과 같습니다.
- Non-repeatable reads : 'READ_COMMITTED'는 Non-repeatable reads 문제를 해결하지 못합니다. 즉, 같은 트랜잭션 내에서 동일한 쿼리를 두 번 실행하면 다른 결과가 반환될 수 있습니다. 이는 다른 트랜잭션에 의해 데이터가 변경되었기 때문입니다.
- Phantom read : 'READ_COMMITTED'는 Phantom read 문제를 해결하지 못합니다. 즉, 같은 트랜잭션 내에서 동일한 쿼리를 두 번 실행하면 새로 생긴 행을 읽을 수 있습니다.. 이는 다른 트랜잭션에 의해 새 행이 추가되었기 때문입니다.
- DMBS 의존성 : 트랜잭션 격리 수준을 명시적으로 설정하지 않으면, 애플리케이션이 DBMS에 의존적이게 됩니다. 즉, 백엔드 DBMS를 변경하면 트랜잭션의 동작이 변경될 수 있습니다. 이러한 의존성은 유지 관리와 이식성 문제를 야기할 수 있습니다.
@Transactional(isolation = Isolation.READ_COMMITTED)
public void somBusinessLogic() {...}
@Transactional(isolation = Isolation.DEFAULT)
public void somBusinessLogic() {...}
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void somBusinessLogic() {...}
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void somBusinessLogic() {...}
@Transactional(isolation = Isolation.SERIALIZABLE)
public void somBusinessLogic() {...}
Dirty Read, Non-Repeatable Read, Phantom Read에 대한 설명은 아래글 참조
2023.07.20 - [Backend] - @Transaction - isolation 격리 수준에 따른 세가지 현상
사용 시 주의할 점
1. 트랜잭션 범위의 이해 : @Transaction 어노테이션을 사용하면 해당 메서드는 새 트랜잭션 컨텍스트에서 실행됩니다. 이는 메서드 실행이 실패하면 트랜잭션이 롤백되며, 성공적으로 완료되면 커밋된다는 것을 의미합니다.
@Transactional
public void someServiceMethod() {
// 트랜잭션이 시작됨
someDaoMethod1(); // 이 메서드는 현재 트랜잭션 내에서 실행
someDaoMethod2(); // 이 메서드는 현재 트랜잭션 내에서 실행
// someServieMethod가 성공적으로 완료되면 트랜잭션이 커밋됨
// 만약 someDaoMethod1 또는 someDaoMethod2에서 예외가 발생하면, 트랜잭션은 롤백됨
}
2. 롤백 규칙의 이해 : @Transaction 어노테이션은 rollbackFor와 noRollbackFor 속성을 제공하여 특정 예외에 대한 롤백 동작을 정의할 수 있습니다. 롤백 규칙을 올바르게 설정하지 않으면 예기치 않은 동작이 발생할 수 있습니다.
3. 프록시 기반 AOP의 이해 : Spring의 @Transactional은 프록시 기반의 AOP(Aspect Oriented Programming)를 사용합니다. 이 말은, 해당 어노테이션을 가진 메서드가 같은 클래스 내의 다른 메서드를 호출할 때는 트랜잭션이 적용되지 않는다는 것을 의미합니다.
4. public 메서드에만 적용 : @Transactional 어노테이션은 public 메서드에만 적용해야 합니다. private, protected, package-private 메서드에 적용하면 어노테이션이 무시됩니다.
5. 성능 고려 : 트랜잭션은 리소스를 많이 사용하므로, 가능한 한 범위를 좁게 유지해야 합니다. 또한, 과도하게 트랜잭션 격리 수준을 유지하면 성능을 저하시킬 수 있으므로 적절한 격리 수준을 설정하는 것이 중요합니다.
6. propagation(트랜잭션 전파의 이해) : 올바른 설정을 통해 트랜잭션 전파 동작을 제어할 수 있어야 합니다.
7. 트랜잭션과 비즈니스 로직의 분리 : 트랜잭션의 관리는 서비스 계층에서 수행해야 하며, 비즈니스 로직은 도메인 계층에서 수행되어야 합니다. 이 두 가지를 혼동하면 코드가 복잡해질 수 있습니다.
'Backend' 카테고리의 다른 글
제어의 역전 - Inversion of Control (0) | 2023.08.11 |
---|---|
Java IO와 NIO (0) | 2023.07.21 |
Java blocking과 non-blocking (0) | 2023.07.21 |
@Transaction - isolation 격리 수준에 따른 세가지 현상 (0) | 2023.07.20 |
메세지 큐(Message Queue) (0) | 2023.03.27 |