
@Transactional 어노테이션은 Spring Framework에서 제공하는 기능으로, 트랜잭션 관리를 편리하게 처리할 수 있게 해주는 매우 중요한 어노테이션임. 주로 데이터베이스와의 작업에서 트랜잭션을 적용할 때 사용되며, 데이터의 일관성, 무결성, 그리고 장애 복구를 보장함.
트랜잭션은 데이터베이스의 연산을 하나의 단위 작업으로 묶어 처리하는 것을 의미함. @Transactional을 사용하면, 해당 메서드에서 실행되는 모든 데이터베이스 작업이 하나의 트랜잭션 내에서 실행됨.
트랜잭션 내에서 예외가 발생하면, 자동으로 롤백이 되어 데이터가 변경되지 않으며, 정상적으로 완료되면 커밋이 되어 변경사항이 확정됨.
트랜잭션이 성공하면 자동으로 커밋해줌.
메서드 실행 중 예외(Exception)가 발생하면 기본적으로 트랜잭션이 롤백됨.
기본 설정으로는 RuntimeException 또는 그 하위 클래스에서만 롤백이 발생하며, checked exception은 롤백되지 않음. 그러나 이 동작을 변경할 수 있음.
트랜잭션의 전파(Propagation)는 트랜잭션이 어떻게 전파될지를 정의함. 예를 들어, 메서드가 기존 트랜잭션을 사용하거나 새 트랜잭션을 시작할지를 설정할 수 있음.
REQUIRED: 기본값이며, 현재 트랜잭션이 있으면 그 트랜잭션을 사용하고, 없으면 새로운 트랜잭션을 시작함.
REQUIRES_NEW: 항상 새로운 트랜잭션을 시작하고, 기존 트랜잭션이 있다면 일시 중지함.
MANDATORY: 반드시 트랜잭션이 있어야 하며, 없으면 예외를 발생시킴.
SUPPORTS: 트랜잭션이 있으면 그 트랜잭션을 사용하고, 없으면 트랜잭션 없이 진행함.
NOT_SUPPORTED: 트랜잭션이 있으면 일시 중지하고, 트랜잭션 없이 실행함.
격리 수준은 동시에 실행되는 여러 트랜잭션 간의 상호작용을 제어하는 설정임. 이를 통해 데이터 일관성을 보장할 수 있음.
DEFAULT: 데이터베이스 기본 격리 수준을 사용함.
READ_UNCOMMITTED: 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽을 수 있음.
READ_COMMITTED: 다른 트랜잭션이 커밋한 데이터만 읽을 수 있음.
REPEATABLE_READ: 트랜잭션 내에서 동일한 쿼리를 여러 번 실행해도 동일한 결과를 보장함.
SERIALIZABLE: 가장 높은 격리 수준으로, 트랜잭션이 순차적으로 실행됨.
트랜잭션이 지정된 시간 내에 완료되지 않으면 롤백되도록 설정할 수 있습니다. 기본값은 -1로 무제한임.
예를 들어, 트랜잭션이 5초 이상 지속되면 자동으로 롤백할 수 있음.
@Transactional을 읽기 전용으로 설정하면, 데이터베이스에서 읽기만 허용하고 쓰기를 금지함. 데이터의 변경이 없음을 보장할 때 사용하며, 성능 최적화에 기여할 수 있음.
이 옵션은 주로 쿼리만 수행하는 메서드에서 설정하는 것이 적합함.
propagation: 트랜잭션의 전파 동작을 정의함.
기본값: Propagation.REQUIRED
isolation: 트랜잭션의 격리 수준을 설정함.
기본값: Isolation.DEFAULT
timeout: 트랜잭션의 최대 수행 시간을 초 단위로 지정함.
기본값: 무제한 (-1)
readOnly: 트랜잭션이 읽기 전용인지 여부를 설정함.
기본값: false
rollbackFor: 지정한 예외가 발생했을 때 롤백을 수행하도록 설정함.
기본값: RuntimeException
noRollbackFor: 지정한 예외가 발생해도 롤백하지 않도록 설정함.
예시 코드
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
public void createOrder(Order order) {
// 주문 생성 로직
}
@Transactional(readOnly = true)
public Order getOrderById(Long orderId) {
// 데이터베이스에서 주문 정보 조회
return orderRepository.findById(orderId).orElseThrow();
}
}
createOrder 메서드: 트랜잭션이 필요하며, REPEATABLE_READ 격리 수준을 사용하고, 트랜잭션 경계 내에서 주문 생성 작업을 수행함.
getOrderById 메서드: 읽기 전용 트랜잭션을 사용하여 데이터베이스에 불필요한 Lock을 걸지 않고 조회 작업만 수행함.
일관성: 트랜잭션은 데이터베이스 상태의 일관성을 보장합니다. 모든 작업이 성공적으로 완료되거나 실패 시 롤백됨.
무결성: 중간 단계에서 문제가 발생하더라도 이전 단계의 변경 사항이 롤백되어 데이터의 무결성을 유지함.
자동 관리: @Transactional 어노테이션을 사용하면 개발자는 트랜잭션 관리의 복잡성을 신경 쓰지 않고 비즈니스 로직에 집중할 수 있음.
@Transactional은 AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)을 기반으로 작동함. 트랜잭션의 관리를 비즈니스 로직과 분리하여 처리하는 방식인데, 이때 프록시(Proxy) 패턴을 활용함. 이를 통해 코드에 직접 트랜잭션 관리 로직을 추가하지 않고, @Transactional을 통해 트랜잭션을 자동으로 관리하게 되는 것임.
스프링은 @Transactional이 붙은 클래스나 메서드가 호출될 때, 해당 메서드 호출을 가로채기 위해 프록시 객체를 생성함. 프록시 객체는 원래 객체 대신 메서드를 호출하고, 트랜잭션 관리를 추가하는 역할을 함.
프록시 객체는 메서드가 호출되기 전에 트랜잭션이 필요한지 확인함. 트랜잭션이 필요하면 트랜잭션을 시작함. 이때, 트랜잭션 매니저가 활성화되어 데이터베이스 연결을 관리하고, 커밋 또는 롤백 여부를 결정함.
트랜잭션을 시작한 후, 비즈니스 로직(메서드 본문)이 실행됨.
비즈니스 로직이 정상적으로 끝나면 트랜잭션을 커밋함.
예외가 발생하면 트랜잭션을 롤백함.
이 모든 과정은 프록시 객체가 처리함.
트랜잭션의 우선순위는 여러 레벨에서 결정될 수 있음. 이를 통해 트랜잭션의 적용 범위와 행동이 결정됨.
클래스에 @Transactional을 붙이면 그 클래스의 모든 메서드에 트랜잭션이 적용됨.
메서드에 개별적으로 @Transactional을 붙이면, 해당 메서드에만 트랜잭션이 적용됨.
우선순위는 메서드 레벨이 클래스 레벨보다 우선함. 즉, 클래스에 트랜잭션을 설정하더라도, 메서드에 별도로 지정된 트랜잭션 설정이 있으면 메서드의 설정이 적용됨.
@Transactional
public class OrderService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createOrder() {
// 메서드에 별도 트랜잭션 설정이 있으면 메서드 설정이 우선
}
}
한 클래스의 메서드에서 또 다른 메서드를 호출할 때, 자기 자신의 메서드를 호출하면 트랜잭션이 적용되지 않을 수 있음. 이는 프록시 기반이기 때문에 발생하는 문제임. 내부 메서드 호출은 프록시 객체가 아닌, 직접 호출이 되어 트랜잭션이 동작하지 않음. 이 경우 트랜잭션 관리가 제대로 되지 않음.
@Transactional
public void methodA() {
methodB(); // methodB에 @Transactional 있어도 트랜잭션 적용 안 됨
}
이를 해결하려면 메서드를 외부에서 호출하거나, 클래스 내에서 메서드 간 호출이 필요하다면 별도의 빈(Bean)으로 분리하는 방식으로 우회 가능함.
트랜잭션 전파 옵션에 따라 트랜잭션을 어떻게 처리할지가 결정됨. 기본적으로는 REQUIRED 전파 속성을 사용하며, 이 설정을 다른 속성으로 변경할 때, 전파 옵션에 따라 기존 트랜잭션을 사용하거나 새로운 트랜잭션을 생성함.
REQUIRES_NEW: 항상 새로운 트랜잭션을 생성.
NESTED: 기존 트랜잭션 안에 중첩 트랜잭션을 생성.
REQUIRED: 기존 트랜잭션을 사용하거나 없으면 새로 생성(기본값).
SUPPORTS: 트랜잭션이 있으면 그 안에서 실행, 없으면 트랜잭션 없이 실행.
NOT_SUPPORTED: 트랜잭션을 사용하지 않음.
NEVER: 트랜잭션이 있으면 예외 발생.
MANDATORY: 반드시 트랜잭션 안에서 실행되어야 함. 없으면 예외 발생.
런타임 예외는 기본적으로 롤백되지만, 체크 예외는 롤백되지 않음.
이를 커스터마이즈하려면 @Transactional(rollbackFor = Exception.class)처럼 설정해 체크 예외도 롤백하도록 할 수 있음.
롤백은 메서드 레벨의 설정이 우선함. 예를 들어 클래스에서 예외에 따라 롤백하도록 설정했더라도, 메서드에서 별도의 롤백 설정을 하면 그 설정이 우선함.
우선순위 적용 예시
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
public class OrderService {
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void createOrder() {
// 이 메서드는 항상 새로운 트랜잭션을 사용하고, 모든 예외에 대해 롤백함.
}
public void cancelOrder() {
// 이 메서드는 클래스의 트랜잭션 설정을 따름 (REQUIRED, 런타임 예외에 대해서만 롤백).
}
}