본문 바로가기
Framework/Spring

[Spring] @Transactional 어노테이션 이해하기(1) 전파유형(Propagation) 과 격리수준(Isolation)

by 곰민 2023. 3. 11.

Spring Boot @Transactional 어노테이션을 이해하기 위해 @Transactional 어노테이션이 무엇이며 제공하는 설정은 무엇인지 알아보겠습니다.

1편에서는 @Transactional 어노테이션이 제공하는 트랜잭션 전파(Transaction Propagation) 및 격리(Transaction Isolation)에 대해서 어떻게 적용되며 어떻게 선언하는지 등 우선적으로 자세하게 알아보도록 하겠습니다.

???????????????

 

트랜잭션(Transacntion)과 ACID

들어가기 앞서

데이터베이스 트랜잭션은 단일 작업 단위로 실행되는 하나 이상의 데이터베이스 작업 시퀀스입니다.
트랜잭션은 ACID 원칙을 적용하여 데이터 일관성과 무결성을 보장합니다.
ACID는 원자성, 일관성, 격리성, 내구성을 의미합니다.

원자성(Atomicity)
트랜잭션은 원자적이어서 분할할 수 없는 단일 작업 단위로 취급됩니다.
트랜잭션의 모든 작업이 완료되거나 완료되지 않을 수 있습니다.
트랜잭션이 어느 시점에서든 실패하면 데이터베이스에 적용된 모든 변경 사항이 롤백되고 데이터베이스는 원래 상태로 돌아갑니다.

일관성(Consistency)
트랜잭션은 트랜잭션 전후에 데이터베이스가 일관성을 유지해야 합니다.
즉, 데이터베이스는 트랜잭션이 실행되기 전과 후에 모두 유효한 상태여야 합니다.
트랜잭션이 기본 키 또는 외래 키 제약 조건과 같은 데이터베이스 제약 조건을 위반하는 경우 트랜잭션은 롤백됩니다.

격리성(Isolation)
트랜잭션은 서로 간섭할 수 없도록 서로 격리되어야 합니다. 다중 사용자 환경에서는 트랜잭션이 동시에 실행되므로 각 트랜잭션이 격리되어 다른 트랜잭션의 결과에 영향을 미치지 않도록 하는 것이 중요합니다.

영속성(Durability)
트랜잭션이 커밋되면 데이터베이스에 대한 변경 내용이 영구적으로 유지되어야 하며 시스템 충돌이나 정전과 같은 후속 장애에도 영향을 받지 않아야 합니다.

 

spring에서는 트랜잭션을 어떻게 처리해 왔을까?

 

spring 3.1

Spring 3.1에는 트랜잭션 지원을 활성화하기 위해 @Configuration 클래스에서 사용할 수 있는 @EnableTransactionManagement 어노테이션이 도입되었습니다.

 

@Configuration  
@EnableTransactionManagement  
public class PersistenceJPAConfig{  
  
    @Bean  
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {  
        //...  
    }  
  
    @Bean  
    public PlatformTransactionManager transactionManager() {  
        JpaTransactionManager transactionManager = new JpaTransactionManager();  
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());  
        return transactionManager;  
    }  
}

 

spring boot and dependency


그러나 Spring Boot 프로젝트를 사용 중이고 클래스 경로에 spring-data-* 또는 spring-tx dependency가 있는 경우 트랜잭션 관리가 기본적으로 활성화됩니다.

주요 dependency 예시

 

1. spring-boot-starter-data-jpa

JPA 기반 데이터 액세스를 사용하는 데 필요한 dependency를 제공.


2. spring-boot-starter-jdbc

spring-jdbc를 포함하여 JDBC 기반 데이터 액세스를 사용하는 데 필요한 dependency을 제공.


3. spring-boot-starter-web

Spring Boot로 웹 애플리케이션을 빌드하는 데 필요한 종속성을 제공.

 

세 가지 dependency 모두 트랜잭션 관리에 필요한 spring-tx도 포함.

 

@Transaction Annotation 이란?


@Transactional은 데이터베이스 작업에서 트랜잭션을 관리하는 데 사용되는 어노테이션입니다. 

트랜잭션을 관리하는 선언적 방법을 제공하며 트랜잭션 어노테이션은 클래스 또는 메서드 레벨에 적용할 수 있습니다. 

클래스 수준에서 적용하면 클래스의 모든 메서드에 대한 기본 트랜잭션 설정을 설정합니다. 

메서드 수준에서 적용하면 해당 특정 메서드의  class-level settings 값을 overrides 합니다.

트랜잭션이 구성되었으므로 이제 클래스 또는 메서드 수준에서 @Transactional 어노테이션을 bean에 달 수 있습니다.

 

@Service 
@Transactional 
public class TestService { 
	//... 
}

 

@Transactional annotation 이 지원 설정들

1. 트랜잭션의 전파 유형

2. 트랜잭션의 격리 수준

3. 트랜잭션에 의해 래핑 된 연산에 대한 시간제한

4. readOnly flag 

     트랜잭션이 readOnly여야 한다는 persistence provider에게 hint 제공

5. 트랜잭션에 대한 롤백 규칙
    기본적으로 롤백은 런타임에 unchecked exceptions에 대해서만 발생.
     checked exception는 트랜잭션의 롤백을 트리거하지 않음.

 

이 중 오늘은 전파 유형과 격리 수준에 대해서 먼저 알아보도록 하겠습니다.

 

트랜잭션 전파 유형(Transaction Propagation Types) 


트랜잭션 어노테이션의 propagation attribute을 사용하여 트랜잭션 전파 유형을 지정할 수도 있습니다.
propagation type은 여러 트랜잭션이 관련된 경우 트랜잭션이 전파되는 방식을 결정합니다.
트랜잭션 전파에는 여러 가지 유형이 존재합니다.

 


1. Required

기본 전파 유형.
트랜잭션이 이미 있는 경우 현재 메서드가 해당 트랜잭션에 참여합니다.
트랜잭션이 존재하지 않으면 메서드에 대한 새 트랜잭션이 생성합니다.

REQUIRED

@Transactional(propagation = Propagation.REQUIRED) 
public void requiredExample(String user) { 
	// ... 
}

 

REQUIRED는 Default 이므로 생략이 가능합니다.

 

@Transactional
public void requiredExample(String user) { 
	// ... 
}

 

 

2. Requires New

이 전파 유형은 항상 새 트랜잭션을 만듭니다
트랜잭션이 이미 존재하는 경우 새 트랜잭션이 완료될 때까지 일시 중단합니다.

Requires New

 

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExample(String user) { 
	// ... 
}

 

3. Nested

이 전파 유형은 중첩된 트랜잭션을 생성함.
트랜잭션이 이미 존재하는 경우 새 트랜잭션이 생성되어 기존 트랜잭션 내에 중첩됨.
트랜잭션이 존재하지 않으면 새 트랜잭션이 생성됨.

nested

@Transactional(propagation = Propagation.NESTED)
public void nestedExample(String user) { 
	// ... 
}

 

4. Supports

이 전파 유형은 트랜잭션이 이미 존재하는 경우 트랜잭션을 지원.
트랜잭션이 존재하면 해당 트랜잭션 내에서 현재 메서드가 실행.
트랜잭션이 존재하지 않으면 트랜잭션 없이 메서드가 실행.

Supports

@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExample(String user) { 
	// ... 
}

 

5. Mandatory

이 전파 유형은 트랜잭션이 이미 존재해야 합니다.
트랜잭션이 존재하지 않으면 예외가 발생합니다.

 

Mandatory

@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExample(String user) { 
	// ... 
}

 

6. Not Supported

이 전파 유형은 트랜잭션을 지원하지 않음.
트랜잭션이 존재하면 현재 메서드가 실행되는 동안 트랜잭션이 일시 중단됨.

 

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExample(String user) {
	// ... 
}

 

7. Never

이 전파 유형은 현재 메서드가 실행될 때 트랜잭션이 존재하지 않는지 확인합니다.
트랜잭션이 존재하면 예외가 발생합니다.

never

@Transactional(propagation = Propagation.NEVER)
public void neverExample(String user) { 
	// ... 
}

 

동시성 부작용(concurrency side effects)과 트랜잭션 격리 수준(Isolation Levels)


 

트랜잭션 격리 수준(Transaction Isolation Levels)은 concurrent 트랜잭션에 의해 적용된 변경 사항이 서로에게 표시되는 방식을 설명합니다.

*concurrent 트랜잭션 : 여러 사용자가 동시에 실행할 수 있는 트랜잭션입니다.

격리 수준은 트랜잭션에서 0개 이상의 동시성 부작용(concurrency side effects)을 방지합니다.

 

 

동시성 부작용(concurrency side effects)


1. Dirty read

concurrent 트랜잭션커밋되지 않은 변경 내용을 읽습니다.


2. Nonrepeatable read

concurrent 트랜잭션동일한 행을 업데이트하고 커밋하는 경우 행을 다시 읽을 때 다른 값을 가져옵니다.


3. Phantom read

다른 트랜잭션이 범위의 일부 행을 추가 또는 제거하고 커밋하는 경우 range-query를 다시 실행한 후 다른 행을 가져옵니다.

 

 

트랜잭션 격리 수준(Transaction Isolation Level)


Default는 DB의 isolation level을 따릅니다

 

1. Read Uncommitted

2. Read committed

3. Repeatable Read

4. Serializable

 

각각 4가지 격리 수준에 대해서 알아보도록 하겠습니다.

Spring Boot JPA 프로젝트에서 기본 격리 수준은 일반적으로 READ_COMMITTED입니다.

 

1. Read Uncommitted

 

@Service
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public class UserService {
  
    @Autowired  
    private UserRepository userRepository;  
  
    public void updateUser(User user) {  
        userRepository.save(user);  
    }  
}

 

 

READ_UNCOMMITTED는 가장 낮은 격리 수준이며 가장 많은 동시 액세스를 허용합니다.
따라서 앞서 언급한 세 가지 동시성 부작용이 모두 발생합니다.

이 격리 수준을 가진 트랜잭션은 다른 concurrent 트랜잭션의 커밋되지 않은 데이터를 읽습니다.

또한 Nonrepeatable readPhantom read가 모두 발생할 수 있습니다. 따라서 행을 다시 읽거나 범위 쿼리를 다시 실행할 때 다른 결과를 얻을 수 있습니다.
메서드 또는 클래스에 대한 격리 수준을 설정할 수 있습니다.

 

Postgres는 READ_UNCOMMITTED 격리를 지원하지 않으며 대신 READ_committed로 되돌아갑니다. 또한 Oracle은 READ_UNCOMMITTED를 지원하거나 허용하지 않습니다.

 


2. Read Committed

 

@Service
@Transactional(isolation = Isolation.READ_COMMITTED)
public class UserService {
  
    @Autowired  
    private UserRepository userRepository;  
  
    public User getUserById(Long id) {  
        return userRepository.findById(id)  
            .orElseThrow(() -> new EntityNotFoundException("User not found"));  
    }  
}

 

READ_COMMITTED는 dirty reads를 방지합니다.
나머지 동시성 부작용은 여전히 발생할 수 있습니다. 
따라서 concurrent 트랜잭션에서 커밋되지 않은 변경 사항은 영향을 미치지 않지만, 트랜잭션이 변경 사항을 커밋하면 다시 쿼리 하여 결과가 변경될 수 있습니다.

Postgres, SQL Server, and Oracle에서 READ_COMMITTED는 the default isolation level입니다 

 

3. Repeatable Read

 

@Service
@Transactional(isolation = Isolation.REPEATABLE_READ)
public class UserService {
  
    @Autowired  
    private UserRepository userRepository;  
  
    public User getUserById(Long id) {  
        return userRepository.findById(id)  
            .orElseThrow(() -> new EntityNotFoundException("User not found"));  
    }  
}

 

REPEATABLE_READDirty read, Nonrepeatable read를 방지합니다. 

따라서 concurrent 트랜잭션에서 커밋되지 않은 변경 사항의 영향을 받지 않습니다.

또한 행에 대해 다시 쿼리해도 다른 결과를 얻지 않습니다. 

그러나 range-queries,를 다시 실행할 때 새로 추가되거나 제거된 행을 얻을 수 있습니다.

두 개 이상의 concurrent 트랜잭션이 동일한 행을 읽고 업데이트할 때 발생하는 업데이트 손실 방지를 위한 최소 요구 수준입니다. 

REPEATABLE_READ는 한 행에 대한 동시 액세스를 전혀 허용하지 않습니다.

 따라서 업데이트 손실이 발생할 수 없습니다.

 

REPEATABLE_READ는 Mysql의 기본 isolation level입니다.
오라클은 REPEATABLE_READ를 지원하지 않습니다.


4. Serializable

 

@Service
@Transactional(isolation = Isolation.SERIALIZABLE)
public class UserService {
  
    @Autowired  
    private UserRepository userRepository;  
  
    public User getUserById(Long id) {  
        return userRepository.findById(id)  
            .orElseThrow(() -> new EntityNotFoundException("User not found"));  
    }  
}

 

SERIALIZABLE은 가장 높은 격리 수준입니다. 

앞서 언급한 모든 동시성 부작용을 방지하지만, concurrent 호출을 순차적으로 실행하기 때문에 concurrent access rate가 가장 낮을 수 있습니다.
즉 트랜잭션 그룹을 정말로 serial 하게 실행하는 것과 동일한 결과를 가져옵니다.

 

 

2편은 아래 링크로 이동하시면 이어서 보실 수 있습니다.
https://colevelup.tistory.com/35

 

[Spring boot] @Transactional 어노테이션 이해하기(2) 롤백(rollback) 규칙, 시간 제한(Timeout), readOnly Flag

Spring Boot @Transactional 어노테이션을 이해하기 위해 @Transactional 어노테이션이 무엇이며 제공하는 설정은 무엇인지. 1편에 이어서 2편에서는 @Transactional 어노테이션이 제공해 주는 트랜잭션 롤백(Tra

colevelup.tistory.com


참조


https://www.baeldung.com/spring-transactional-propagation-isolation
https://stackoverflow.com/questions/8490852/spring-transactional-isolation-propagation
https://dzone.com/articles/spring-transaction-propagation

https://www.marcobehler.com/guides/spring-transaction-management-transactional-in-depth

반응형

댓글