본문 바로가기

IT

스프링 트랜잭션 (Spring Transaction) step1

728x90

트랜잭션은 데이터베이스 시스템에서 데이터를 일관성있게 처리하기 위한 단위 작업 단위이다. 데이터베이스에 대한 변경 작업은 여러 개의 SQL 문으로 이루어져 있을 수 있는데, 이러한 SQL 문을 하나의 논리적인 작업으로 묶어서 처리하는 것이다.

트랜잭션은 아래와 같은 네가지 특징을 가진다.

  1. 원자성(Atomicity): 트랜잭션에 포함된 작업은 전부 성공하거나 전부 실패해야 한다. 즉, 트랜잭션 내의 모든 작업은 한 개의 논리적인 단위로 간주되어야 하며, 어떤 작업이라도 실패하면 트랜잭션 전체가 실패해야 한다.
  2. 일관성(Consistency): 트랜잭션은 데이터베이스에 일관성 있는 상태를 유지해야 한다. 즉, 트랜잭션을 수행하기 전과 후의 데이터베이스 상태가 일관성 있어야 한다.
  3. 격리성(Isolation): 동시에 실행되는 다른 트랜잭션의 작업에 영향을 받지 않도록 격리되어야 한다. 즉, 동시에 실행되는 다른 트랜잭션으로부터 각각 독립적으로 작동해야 한다.
  4. 지속성(Durability): 트랜잭션이 성공적으로 완료된 후에는 영구적으로 데이터베이스에 반영되어야 한다. 즉, 시스템 장애나 다른 문제가 발생해도 트랜잭션 결과는 유지되어야 한다.

위의 특징들은 ACID라고 불리는 트랜잭션의 4가지 속성으로 알려져 있다. ACID 속성을 준수하는 트랜잭션은 데이터베이스 시스템에서 안전하고 일관성 있는 데이터 처리를 보장할 수 있습니다.

 

스프링 프레임워크에서 제공하는 트랜잭션은 기본적으로 데이터베이스 트랜잭션을 구현하는 데 사용한다. 그러나 스프링 트랜잭션은 일반적인 데이터베이스 트랜잭션의 기능뿐만 아니라, 분산 환경에서의 트랜잭션 처리나 비트랜잭션(예: 메시지 큐)과 같은 다양한 트랜잭션 환경에서도 사용할 수 있다.

 

Spring은 트랜잭션과 관련된 3가지 핵심 기술을 제공하고 있다. 그 3가지 핵심 기술은 다음과 같다.

  1. 트랜잭션(Transaction) 동기화
  2. 트랜잭션(Transaction) 추상화
  3. AOP(Aspect-Oriented Programming)를 이용한 트랜잭션(Transaction) 분리

1. 트랜잭션 동기화

트랜잭션 동기화는 데이터베이스에서 여러 개의 트랜잭션이 동시에 실행될 때, 각각의 트랜잭션이 서로 영향을 미치지 않도록 제어하는 것을 말한다.

여러 개의 트랜잭션이 동시에 실행되면서 동일한 데이터에 접근하는 경우, 데이터의 일관성을 유지하기 위해 트랜잭션 간의 상호작용을 관리하는 것이 중요하다다. 이를 위해 데이터베이스 시스템은 트랜잭션 동기화를 사용한다.

트랜잭션 동기화는 주로 락(lock)과 트랜잭션 격리 수준(isolation level)을 이용하여 구현됩니다. 락은 데이터베이스에서 특정 데이터에 대한 접근을 제한하여 다른 트랜잭션이 해당 데이터를 변경하지 못하도록 한다. 격리 수준은 여러 개의 트랜잭션이 동시에 실행될 때, 각각의 트랜잭션 간에 데이터를 어떻게 공유할지 결정하게 된다. 이로써 트랜잭션 동기화를 통해 데이터베이스에서 데이터의 일관성과 무결성을 보장할 수 있습니다.

스프링에서 제공하는 트랙잭션 동기화는 여러 스프링 빈에서 공유하는 트랜잭션 정보를 동기화하여 사용할 수 있도록 지원한다. 각각의 메서드가 독립적으로 트랜잭션을 처리하게 되면 데이터 불일치 문제가 발생할 수 있어 스프링은 트랜잭션 동기화라는 메커니즘을 제공한다. 

트랜잭션 매니저와 AOP를 통해 동작한다. 트랜잭션 매니저가 트랜잭션을 시작하면, 해당 트랜잭션을 처리하는 스레드에서 트랜잭션 동기화 메커니즘이 실행되어 트랜잭션 정보를 저장한다. 그리고 다른 스프링 빈에서 트랜잭션 처리를 수행할 때 트랜잭션 동기화 메커니즘을 통해 저장된 트랜잭션 정보를 공유하여 사용한다. 이로써 여러 스프링 빈에서 트랜잭션을 처리할 때 트랜잭션 정보가 서로 충돌하지 않고 일관성 있게 처리될 수 있다. 이는 데이터 불일치 문제를 예방하고, 안전한 트랜잭션 처리를 가능하게 한다. 

 

2. 트랜잭션 추상화

트랜잭션 추상화는 데이터베이스 트랜잭션을 다루기 위한 추상화된 인터페이스를 제공하는 것을 말한다.

트랜잭션 추상화 계층

스프링은 트랜잭션 기술의 공통점을 담은 트랜잭션 추상화 기술을 제공하고 있다. 이를 이용하면 애플리케이션에서 직접 각 기술의 트랜잭션 API를 이용하지 않고도 일관된 방식으로 트랜잭션을 제어하는 트랜잭션 경계 설정 작업이 가능해진다. 스프링에서는 TransactionDefinition, TransactionStatus, TransactionManager 등의 인터페이스와 클래스를 제공하여 트랜잭션 추상화를 지원한다. 이를 사용하면 데이터베이스 뿐 아니라 JMS, JPA, JTA 등 다양한 트랜잭션 리소스에서도 일관된 방식으로 트랜잭션을 다룰 수 있다. JDBC의 경우 Connection을 사용하지만 Hibernate는 Session 객체를 사용하니 이러한 문제도 해결할 수 있는 것이다. 자연스럽게 트랜잭션 추상화에서 전략패턴이 녹아져있음을 볼 수 있다. 

 

3. AOP(Aspect-Oriented Programming)를 이용한 트랜잭션 분리

이 방법은 트랜잭션과 같이 여러 개의 메소드를 공통적으로 사용되는 기능을 분리하여 코드의 중복을 최소화하고 유지보수성을 높이는 방법이다.

 

AOP가 적용되지 않은 경우 비즈니스 로직과 트랜잭션 로직을 addusers 함수가 책임지고 있다. 좋지 않은 예이다. 

public void addUsers(List<User> userList) {
	TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
	
	try {
		for (User user: userList) {
			if(isEmailNotDuplicated(user.getEmail())){
				userRepository.save(user);
			}
		}

		this.transactionManager.commit(status);
	} catch (Exception e) {
		this.transactionManager.rollback(status);
		throw e
	}
}

 

트랜잭션을 담당하는 기술 코드를 완전히 분리시키기위해 해당 로직을 클래스 밖으로 빼내서 별도의 모듈로 만드는 AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)를 적용하면 핵심 비즈니스 로직만 남길 수 있다.

@Service
@RequiredArgsConstructor
@Transactional
public class UserService {

    private final UserRepository userRepository;

    public void addUsers(List<User> userList) {
        for (User user : userList) {
            if (isEmailNotDuplicated(user.getEmail())) {
                userRepository.save(user);
            }
        }
    }
}

지금은 큰 그림을 위주로 트랜잭션, 스프링 트랜잭션에 대해 알아보았다.

하지만 어떻게 트랜잭션 어노테이션이 이 역할을 해주는지 궁금증이 생겼고 과거에 어떤 문제를 만나 어떻게 해결해 나갔는지 토비의 스프링 3.1을 참고하여 이해할 수 있었고 해석한 내용을 step2에서 부터 작성하려고 한다. 

 

@Transactional이 도입되기 전까지 어떤 노력들이 있었는지에 대해 다음 step에서 확인해보자.