우당탕탕
[Spring] 스프링 @Transactional(트랜잭션)에 대해 이해하기 본문
@Transactional ( 트랜잭션 )에 대한 이해
트랜잭션의 개념
트랜잭션은 데이터베이스에서 수행되는 작업의 묶음으로, 일련의 작업이 모두 성공적으로 완료되거나 모두 실패해야 하는 특성을 가집니다. 이를 ACID 속성이라고 하며, 아래와 같은 특징을 가집니다
- 원자성(Atomicity): 트랜잭션 내의 모든 작업이 성공해야만 데이터베이스에 반영됩니다. 하나라도 실패하면 전체가 롤백되어야 합니다. ( ALL OR NOTHING )
- 일관성(Consistency): 트랜잭션이 완료되면 데이터베이스는 일관된 상태를 유지해야 합니다.
- 격리성(Isolation): 동시에 실행되는 트랜잭션 간에 서로 영향을 미치지 않아야 합니다.
- 지속성(Durability): 트랜잭션이 성공적으로 완료된 후에는 그 결과가 영구적으로 저장되어야 합니다.
스프링의 @Transactional 어노테이션
스프링에서 @Transactional은 트랜잭션을 쉽게 관리할 수 있도록 도와주는 특별한 도구(어노테이션)입니다. 이 어노테이션을 사용하면 어떤 작업이 트랜잭션으로 묶여야 하는지를 스프링에게 알려줄 수 있게 됩니다.
예를 들어, 사용자를 데이터베이스에 저장하는 메서드에 @Transactional을 붙이면, 그 메서드가 실행되는 동안 모든 작업이 트랜잭션으로 처리됩니다. 만약 중간에 문제가 생기면, 스프링이 자동으로 롤백(이전상태로 돌림) 해 줍니다.
AOP와 @Transactional의 관계
AOP는 Aspect-Oriented Programming의 약자입니다. 쉽게 말해, 프로그램의 특정 부분(예: 트랜잭션 관리)을 다른 코드와 분리해서 관리하는 방법입니다.
스프링에서 @Transactional은 기본적으로 AOP를 사용하여 동작합니다. AOP를 통해 트랜잭션을 관리하면, 코드의 여러 부분에서 트랜잭션 관리 로직을 중복해서 작성할 필요가 없습니다. 대신, 트랜잭션이 필요한 메서드에 @Transactional을 붙이기만 하면 스프링이 자동으로 트랜잭션을 시작하고 종료해 줍니다.
AOP는 "관심사 분리"를 통해 코드의 가독성과 유지 보수성을 높여줍니다. 즉, 트랜잭션 관리처럼 반복적으로 필요한 작업을 한 곳에서 처리하고, 비즈니스 로직은 더 간결하게 유지할 수 있도록 도와주는 것이죠.
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional // 트랜잭션 어노테이션 선언
public void createUser(String name, String email) {
User user = new User();
user.setName(name);
user.setEmail(email);
userRepository.save(user);
// 예외 발생 시 트랜잭션 롤백을 테스트할 수 있습니다.
if (email.equals("error@example.com")) {
throw new RuntimeException("Rollback transaction");
}
}
}
트랜잭션을 처리하는 createUser라는 메서드에 @Transactional을 적용시킨 코드입니다. 정상적인 이메일이 createUser 메서드에 들어가는 경우 정상적으로 데이터베이스에 저장이 되지만 예외가 발생시에는 @Transactional에 의해 롤백처리됩니다.
트랜잭션 전파방식 (Propagation)
전파 방식은 트랜잭션이 어떻게 전파되는지를 결정하는 설정입니다. 여러 메서드가 호출될 때, 한 메서드에서 트랜잭션이 시작되면 그 트랜잭션을 다른 메서드가 어떻게 사용할지를 정의합니다.
- REQUIRED: 현재 트랜잭션이 있다면 그 트랜잭션을 사용하고, 없다면 새로 시작합니다.
- 간단 예시: 친구가 이미 아이스크림을 사기 위해 돈을 모아두고 있으면, 그 돈을 사용해서 아이스크림을 사는 것.
@Transactional(propagation = Propagation.REQUIRED)
- REQUIRES_NEW: 항상 새로운 트랜잭션을 시작합니다. 현재 트랜잭션이 있다면 일시 중지하고 새로 시작합니다.
- 간단 예시: 친구가 새로운 게임을 시작하길 원하면, 기존 게임을 잠시 멈추고 새로운 게임을 시작하는 것.
-
@Transactional(propagation = Propagation.REQUIRES_NEW)
- NESTED: 중첩된 트랜잭션을 지원합니다. 부모 트랜잭션이 끝날 때 자식 트랜잭션도 같이 끝납니다.
- 간단 예시: 부모가 요리를 하면서 자녀가 간식을 준비하면, 부모가 요리를 마치면 자녀의 간식도 같이 마무리되는 것.
-
@Transactional(propagation = Propagation.NESTED)
트랜잭션 격리 수준 (Isolation)
격리 수준은 여러 트랜잭션이 동시에 실행될 때 데이터의 일관성을 어떻게 유지할지를 결정합니다. 주요 격리 수준은 다음과 같습니다:
- DEFAULT: 데이터베이스의 기본 격리 수준을 사용합니다.
-
@Transactional(isolation = Isolation.DEFAULT)
-
- READ_UNCOMMITTED: 다른 트랜잭션이 커밋하지 않은 데이터를 읽을 수 있습니다. 마치 친구가 아직 사지 않은 아이스크림을 미리 보는 것과 비슷합니다.
-
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
-
- READ_COMMITTED: 다른 트랜잭션이 커밋한 데이터만 읽을 수 있습니다. 친구가 아이스크림을 사고 나서야 그 아이스크림을 확인할 수 있는 것이죠.
-
@Transactional(isolation = Isolation.READ_COMMITTED)
-
- REPEATABLE_READ: 같은 트랜잭션 내에서 동일한 쿼리를 여러 번 수행할 경우 항상 같은 결과를 보장합니다.
-
@Transactional(isolation = Isolation.REPEATABLE_READ)
-
- SERIALIZABLE: 가장 높은 격리 수준으로, 트랜잭션이 완전히 순차적으로 실행됩니다.
-
@Transactional(isolation = Isolation.SERIALIZABLE)
-
각 서비스의 특성과 업무 요구에 따라 적절한 트랜잭션 격리 수준과 전파 방식을 선택하는 것이 매우 중요합니다.
예를 들어, 데이터의 일관성이 최우선인 경우에는 높은 격리 수준을 적용해야 할 수 있으며, 여러 작업이 동시에 실행되는 환경에서는 적절한 전파 방식을 고려해야 합니다. 이러한 선택은 애플리케이션의 성능과 안정성에 큰 영향을 미칠 수 있으니 알맞게 선택하여 사용하시면 될 것 같습니다!
'Tech > Spring' 카테고리의 다른 글
[Spring] 초보자를 위한 Spring Security: JWT로 웹 앱 보안 강화하기 (0) | 2024.09.14 |
---|---|
[Spring] JPA에서 Persistable 인터페이스로 성능 최적화하기: isNew() 메서드 활용법 (0) | 2024.08.16 |
[Spring] ControllerAdvice와 ExceptionHandler에 대해 (1) | 2022.09.27 |
[Spring] IoC(Inversion of Control)과 DI(Dependency Injection) 이란? (0) | 2022.09.01 |
[Spring] 필터(Filter)와 인터셉터(Interceptor) 차이 (1) | 2022.08.23 |