트랜잭션은 데이터베이스에서 하나의 작업 단위를 말하며, 여러 작업이 하나의 논리적인 작업으로 실행되어야 할 때 사용됩니다. Spring Boot는 트랜잭션 관리를 쉽게 하기 위해 Spring Transaction Management를 제공합니다.
Spring에서 트랜잭션을 선언적으로 관리하기 위해 사용됩니다.
기본적인 예
@Service
public class UserService {
@Transactional
public void createUser(User user) {
userRepository.save(user);
// 예외 발생 시, 이전 작업은 롤백됩니다.
if (user.getName().isEmpty()) {
throw new RuntimeException("Name cannot be empty");
}
}
}
트랜잭션의 전파 방식을 정의합니다.
REQUIRED
: 기본 설정, 이미 진행 중인 트랜잭션이 있다면 이를 사용하고, 없으면 새로 시작합니다.REQUIRES_NEW
: 항상 새로운 트랜잭션을 시작하며, 기존 트랜잭션은 보류됩니다.MANDATORY
: 기존 트랜잭션이 없으면 예외를 발생시킵니다.SUPPORTS
: 트랜잭션이 있으면 사용하고, 없으면 트랜잭션 없이 실행합니다.예
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void performAnotherTask() {
// 새로운 트랜잭션에서 작업 수행
}
트랜잭션이 다른 트랜잭션과 데이터를 공유하는 방식을 정의합니다.
READ_UNCOMMITTED
: 커밋되지 않은 데이터를 읽을 수 있음.READ_COMMITTED
: 커밋된 데이터만 읽음.REPEATABLE_READ
: 트랜잭션 동안 동일 데이터를 읽을 수 있음.SERIALIZABLE
: 완벽한 격리, 동시 실행 불가.예
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void processTransaction() {
// 데이터 일관성 보장
}
프록시는 트랜잭션 관리의 내부 구현에 중요한 역할을 합니다. Spring은 프록시 객체를 사용하여 트랜잭션의 시작, 커밋, 롤백 등을 처리합니다.
Spring은 AOP(Aspect-Oriented Programming)를 통해 트랜잭션 관리를 구현합니다. @Transactional 어노테이션이 적용된 메서드에 대해 프록시 객체가 생성되며, 이 객체가 트랜잭션 경계를 관리합니다.
프록시 동작 과정
UserService
의 createUser()
메서드를 호출.UserService.createUser()
메서드가 실행됩니다.commit()
.rollback()
.프록시 생성 방식
Spring은 두 가지 방식으로 프록시를 생성합니다.
트랜잭션이 선언된 메서드가 같은 클래스의 다른 메서드에서 호출될 경우, 프록시가 트랜잭션 경계를 인식하지 못합니다.
문제 예
@Service
public class UserService {
@Transactional
public void methodA() {
methodB(); // 트랜잭션 적용 안됨
}
@Transactional
public void methodB() {
// 트랜잭션 필요 작업
}
}
해결 방법
TransactionTemplate
을 사용하여 트랜잭션을 수동 관리.Hibernate는 영속성 컨텍스트(EntityManager)에 의해 관리되는 엔티티에 트랜잭션이 적용됩니다. 트랜잭션이 종료되면 영속성 컨텍스트가 닫히므로, 이후 작업에서 Lazy Loading 문제가 발생할 수 있습니다.
해결 방법
FetchType.EAGER
로 설정하여 즉시 로딩.@Transactional
로 선언적 트랜잭션 관리 가능.spring.jpa.show-sql=true
와 logging.level.org.springframework.transaction=DEBUG
설정으로 트랜잭션 경계를 로그로 확인 가능합니다.