트랜잭션이란?
트랜잭션(Transaction)은 데이터베이스의 상태를 변화시키기 위해 수행하는 일련의 연산들을 말한다. 트랜잭션은 데이터의 무결성과 일관성을 보장하기 위해 ACID 속성을 따른다.
- 원자성(Atomicity): 트랜잭션 내의 모든 작업이 완벽하게 수행되거나 전혀 수행되지 않아야 한다.
- 일관성(Consistency): 트랜잭션이 성공적으로 완료되면 데이터베이스는 일관성 있는 상태를 유지해야 한다.
- 격리성(Isolation): 트랜잭션이 실행되는 동안 다른 트랜잭션의 영향을 받아서는 안 된다.
- 지속성(Durability): 트랜잭션이 완료되면 그 결과는 영구적으로 반영되어야 한다.
JPA와 트랜잭션 처리
JPA에서 트랜잭션 관리는 주로 EntityManager
를 통해 이루어진다. 하지만 Spring Data JPA를 사용하면 더 간단하게 트랜잭션을 관리할 수 있다. Spring에서는 @Transactional
어노테이션을 사용하여 트랜잭션을 선언적으로 관리할 수 있다.
@Transactional 어노테이션
@Transactional
은 트랜잭션 범위를 설정하고 관리하는 데 사용되는 어노테이션이다. 메소드나 클래스 수준에서 적용할 수 있다.
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional
public void updateUser(Long id, String newUsername) {
User user = userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
user.setUsername(newUsername);
userRepository.save(user);
}
}
@Transactional
: 메소드나 클래스에 적용하여 해당 범위 내에서 트랜잭션을 관리한다.
@Transactional에서 사용되는 어노테이션
- @Transactional(propagation = Propagation.REQUIRED): 기본 전파 수준으로, 현재 트랜잭션이 존재하면 해당 트랜잭션을 사용하고, 없으면 새로운 트랜잭션을 생성한다.
@Transactional(propagation = Propagation.REQUIRED)
public void saveNewUser(User user) {
userRepository.save(user);
}
- @Transactional(readOnly = true): 읽기 전용 트랜잭션을 설정하여 성능을 최적화한다.
@Transactional(readOnly = true)
public User getUser(Long id) {
return userRepository.findById(id).orElse(null);
}
- @Transactional(rollbackFor = Exception.class): 특정 예외가 발생했을 때 트랜잭션을 롤백한다.
@Transactional(rollbackFor = Exception.class)
public void riskyOperation() throws Exception {
// 예외가 발생하면 트랜잭션 롤백
}
- @Transactional(isolation = Isolation.SERIALIZABLE): 트랜잭션의 격리 수준을 설정한다.
@Transactional(isolation = Isolation.SERIALIZABLE)
public void performCriticalOperation() {
// 중요한 데이터베이스 작업 수행
}
트랜잭션 전파
트랜잭션 전파는 트랜잭션이 다른 트랜잭션 메소드를 호출할 때 어떻게 동작할지를 정의한다. Spring은 다음과 같은 전파 옵션을 제공한다:
- REQUIRED (기본값): 현재 트랜잭션이 존재하면 해당 트랜잭션을 사용하고, 없으면 새로운 트랜잭션을 생성한다.
- REQUIRES_NEW: 항상 새로운 트랜잭션을 생성한다. 기존 트랜잭션은 일시 중단된다.
- MANDATORY: 현재 트랜잭션이 존재해야 한다. 존재하지 않으면 예외를 발생시킨다.
- SUPPORTS: 현재 트랜잭션이 존재하면 해당 트랜잭션을 사용하고, 없으면 트랜잭션 없이 실행한다.
- NOT_SUPPORTED: 트랜잭션 없이 실행한다. 기존 트랜잭션은 일시 중단된다.
- NEVER: 트랜잭션이 존재하면 예외를 발생시킨다.
- NESTED: 중첩된 트랜잭션을 생성한다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveNewUser(User user) {
userRepository.save(user);
}
트랜잭션 격리 수준
데이터베이스 트랜잭션 격리 수준은 트랜잭션이 다른 트랜잭션에 얼마나 영향을 받는지를 정의한다. 격리 수준은 다음과 같다:
- READ UNCOMMITTED: 다른 트랜잭션이 커밋되지 않은 데이터를 읽을 수 있다.
- READ COMMITTED: 다른 트랜잭션이 커밋한 데이터만 읽을 수 있다.
- REPEATABLE READ: 트랜잭션이 시작된 이후에 커밋된 데이터만 읽을 수 있다.
- SERIALIZABLE: 가장 높은 격리 수준으로, 트랜잭션 간에 완전한 격리를 보장한다.
@Transactional(isolation = Isolation.SERIALIZABLE)
public void performCriticalOperation() {
// 중요한 데이터베이스 작업 수행
}
격리 수준의 선택
- READ UNCOMMITTED: 성능이 중요하고 일관성이 크게 중요하지 않은 경우 사용.
- READ COMMITTED: 대부분의 애플리케이션에서 기본으로 사용.
- REPEATABLE READ: 일관성이 중요한 애플리케이션에서 사용.
- SERIALIZABLE: 데이터 일관성이 매우 중요한 경우 사용하지만, 성능 저하가 발생할 수 있다.
트랜잭션 롤백
@Transactional
어노테이션을 사용하면 특정 예외가 발생했을 때 트랜잭션을 롤백할 수 있다. rollbackFor
속성을 사용하여 롤백할 예외를 지정할 수 있다.
@Transactional(rollbackFor = Exception.class)
public void riskyOperation() throws Exception {
// 예외가 발생하면 트랜잭션 롤백
}
트랜잭션 처리와 성능 최적화
트랜잭션 처리는 데이터베이스 성능에 영향을 미칠 수 있다. 트랜잭션 범위를 최소화하고, 필요한 경우 트랜잭션 격리 수준을 낮추어 성능을 최적화할 수 있다.
트랜잭션 범위 최소화
트랜잭션의 범위를 최소화하여 데이터베이스 잠금 시간을 줄이고 성능을 향상시킬 수 있다. 예를 들어, 필요한 최소한의 코드 블록만 트랜잭션 내에서 실행하도록 한다.
@Transactional
public void updateMultipleUsers(List<User> users) {
for (User user : users) {
userRepository.save(user);
}
}
캐싱 사용
캐싱을 사용하여 동일한 데이터에 대한 반복적인 데이터베이스 접근을 줄일 수 있다. Spring과 Hibernate는 1차 캐시와 2차 캐시를 지원한다.
마무리
JPA와 Spring Data JPA를 사용하면 트랜잭션을 쉽게 관리할 수 있다. 트랜잭션 전파, 격리 수준, 롤백 설정 등을 통해 데이터의 일관성과 무결성을 유지하면서 성능을 최적화할 수 있고, 트랜잭션 관리는 데이터베이스와의 상호작용에서 매우 중요한 부분이다. 트랜잭션 관리 기능을 잘 이해하고 활용하는 것이 정말 중요할 것 같다.
'개념 정리 > Spring' 카테고리의 다른 글
Spring Data JPA 이해하기: 주요 구성 요소와 어노테이션 (0) | 2024.07.19 |
---|---|
JPA 이해하기: 동작 원리와 핵심 구성 요소 (0) | 2024.07.19 |