본문 바로가기
학습/Spring

[spring] @Transactional 기초 탐구

by KKambi 2021. 6. 1.

스프링 부트로 개발을 하다 보면 트랜잭션이 필요한 부분에 @Transactional 어노테이션을 사용하게 됩니다. 그런데 어느 날 해당 어노테이션에 대해 질문을 받게 되었고 어버버 거리는 제 자신을 발견할 수 있었어요. 그래서 이 기회에 좀 더 공부하려 합니다.

 

 

사전 지식 - 트랜잭션의 정의

A database transaction symbolizes a unit of work performed within a database management system (or similar system) against a database, and treated in a coherent and reliable way independent of other transactions. A transaction generally represents any change in a database.

데이터베이스 트랜잭션은 DBMS 내부에서 수행되는 작업의 단위를 의미하며, 다른 트랜잭션과 독립적으로 일관적이고 신뢰할 수 있는 방법으로 여겨집니다. 트랜잭션은 일반적으로 데이터베이스에서의 어떤 변화를 표현하게 됩니다.

 

In a database management system, a transaction is a single unit of logic or work, sometimes made up of multiple operations. Any logical calculation done in a consistent mode in a database is known as a transaction.

DBMS에서 트랜잭션은 하나의 작업이거나, 다수의 연산으로 이루어집니다. 데이터베이스 내부에서 일관적으로 수행되는 논리적인 연산을 트랜잭션이라 부릅니다.

 

A database transaction, by definition, must be atomic (it must either be complete in its entirety or have no effect whatsoever), consistent (it must conform to existing constraints in the database), isolated (it must not affect other transactions) and durable (it must get written to persistent storage).

Database practitioners often refer to these properties of database transactions using the acronym ACID.

데이터베이스 트랜잭션은 1)원자성 2)일관성 3)격리성 4)지속성을 가집니다.
1) 트랜잭션 내 연산들이 모두 수행되거나, 하나도 실행되지 않아야 합니다
2) 데이터베이스에 존재하는 제약들을 따라야 합니다 (트랜잭션 수행 후에도 일관적인 상태 유지)
3) 다른 트랜잭션에 영향을 끼칠 수 없습니다
4) 성공한 트랜잭션의 결과는 데이터베이스에 영구적으로 남아있어야 합니다

 

 

사전 지식 - 트랜잭션의 목적

Databases and other data stores which treat the integrity of data as paramount often include the ability to handle transactions to maintain the integrity of data. A single transaction consists of one or more independent units of work, each reading and/or writing information to a database or other data store. When this happens it is often important to ensure that all such processing leaves the database or data store in a consistent state.

데이터 무결성(Data Integrity)를 중요시하는 데이터 스토어는 무결성을 유지하기 위해 트랜잭션을 사용합니다. 트랜잭션은 데이터 저장소에 정보를 읽거나 쓰는 독립적인 연산들로 구성됩니다. 이 때 모든 연산들이 데이터 스토어를 일관적인 상태로 유지할 수 있도록 보장하는 것이 중요합니다.

 

 

사전 지식 - 트랜잭션의 수행 과정

A simple transaction is usually issued to the database system in a language like SQL wrapped in a transaction, using a pattern similar to the following:
1. Begin the transaction.
2. Execute a set of data manipulations and/or queries.
3. If no error occurs, then commit the transaction.
4. If an error occurs, then roll back the transaction.

SQL과 같은 언어를 사용하여 트랜잭션을 감싸는 데이터베이스 시스템에선, 트랜잭션은 보통 다음과 같은 순서로 수행됩니다.
1) 트랜잭션을 시작(begin합니다
2) 트랜잭션 내부 연산들을 수행합니다
3) 에러가 발생하지 않았다면 트랜잭션을 기록(commit)합니다
4) 에러가 발생했다면 트랜잭션을 되돌립니다(rollback) = 트랜잭션 수행 전의 상태로 되돌립니다

 

A transaction commit operation persists all the results of data manipulations within the scope of the transaction to the database. A transaction rollback operation does not persist the partial results of data manipulations within the scope of the transaction to the database. In no case can a partial transaction be committed to the database since that would leave the database in an inconsistent state.

트랜잭션 commit은 트랜잭션에 속한 모든 연산들의 결과를 저장합니다. 반대로 트랜잭션 rollback의 경우 어떤 연산 결과도 저장하지 않습니다. 따라서 데이터베이스를 일관적이지 않은(inconsistent), 무결성을 위반한 상태로 만들지 않습니다. 트랜잭션의 연산들은 부분적으로 커밋되지 않기 때문입니다.

 

 


그럼 @Transactional은 뭔데?

스프링에선 트랜잭션 매니저에서 트랜잭션을 얻어오는 Programmatic Transaction을 사용할 수도 있지만, 가독성을 떨어뜨리거나 휴먼 에러를 유발할 수 있기 때문에 잘 사용하지 않습니다.

Declarative Transaction으로 트랜잭션을 관리하게 되면 스프링의 AOP를 적극적으로 체감할 수 있습니다. 이 때 xml에서 AOP 설정으로 트랜잭션을 선언하거나, 어노테이션을 사용할 수 있습니다. 이 때 @Transactional을 사용합니다.

  • 스프링은 해당 메소드를 감싼 프록시 객체를 생성
  • 메소드 앞뒤에 트랜잭션 로직을 삽입 (begin - commit/rollback)
  • public method에만 프록시 객체 생성

 

다음은 Spring docs에서 발췌한 @Transactional 설명입니다.

개별 메소드나 클래스에 붙을 수 있는 트랜잭션 특성입니다. 클래스 레벨에서 어노테이션은 선언된 클래스와 그의 서브클래스 내부에 있는 모든 메소드에 기본으로 적용됩니다. 클래스 계층 구조에서 조상 클래스로 올라가 적용되진 않습니다.  커스텀 롤백 룰이 적용되지 않았다면, 트랜잭션은 기본적으로 RuntimeException과 Error가 발생했을 때 롤백합니다. 하지만 Checked Exception에 대해선 롤백되지 않습니다.

해당 어노테이션은 일반적으로 PlatformTrasactionManager에 의해 관리되는 쓰레드-종속적인 트랜잭션으로 수행됩니다. 해당 쓰레드 안에서 수행되는 데이터 접근 연산들에 트랜잭션이 노출됩니다. 주의할 점은 @Transactional이 적용된 메소드 내부에서 새롭게 시작되는 쓰레드에는 해당 트랜잭션이 전파되지 않는다는 것입니다. (메소드를 수행하며 새롭게 생성된 쓰레드에선 어떤 연산도 기존 트랜잭션에 포함되지 않음)

 

 

@Transactional Attribute

다음은 어노테이션이 가질 수 있는 대표적인 속성입니다. 종류가 매우 다양하니 간단하게 알아볼게요.

속성 설명 비고
isolation 트랜잭션 격리 수준 별도로 정의하지 않으면 DB의 Isolation Level
propagation 트랜잭션 전파 규칙 Default=REQUIRED
readOnly 읽기 전용 모드 Default=false
noRollbackFor 해당 예외가 발생했을 때 롤백X  
rollbackFor 해당 예외가 발생했을 때 롤백O  
timeout 타임아웃 설정 Default=-1 (no timeout)
value 사용할 트랜잭션 매니저 설정 Bean의 qualifier 지정

 

isolation

Isolation Level은 트랜잭션의 주요 성질인 ACID 중 격리성(Isolation)을 구현하는 수준입니다. 서로 다른 트랜잭션들이 서로 얼마나 격리되어 있는지 나타내며, 수준을 높일수록 동시성을 떨어뜨리기도 합니다. 어떤 수준의 락을 거느냐와 밀접하게 관련되어 있습니다.

  • DEFAULT : 데이터베이스의 격리 수준 따름
  • READ_UNCOMMITTED : Dirty Read가 발생한다. 커밋되지 않은 다른 트랜잭션의 변경 내용도 읽어오기 때문이다. 정합성이 매우 떨어지므로 거의 사용하지 않는다. 
  • READ_COMMITTED : 커밋된 다른 트랜잭션의 변경 내용만 읽어온다. 그러나 Non-Repetable read 문제가 발생한다. 한 트랜잭션에서 처음 읽었던 행의 정보가 커밋된 다른 트랜잭션의 UPDATE로 변경되면, 다시 읽었을 때 그 행의 정보가 달라진다.
  • REPEATABLE_READ : 트랜잭션이 시작되기 이전에 커밋된 내용만 읽어올 수 있다. 트랜잭션 내에서 SELECT하는 데이터에 대해 Consistent Read를 보장한다. Shared Lock을 걸기 때문이다. 그러나 Phantom read 문제가 발생한다. 커밋된 다른 트랜잭션의 INSERT로 새로운 행이 삽입되면, 한 트랜잭션에서 다시 동일한 SELECT 쿼리를 수행했을 때 새로운 행(=phantom)이 추가로 조회된다.
  • SERIALIZABLE : 트랜잭션 내에서 SELECT하는 데이터에 대해 Exclusive Lock을 건다. 격리 수준이 제일 높고 엄격하다. 너무 엄격해서 성능이 떨어지기도 하고, 데드락이 발생할 수도 있다.

MySQL의 데이터베이스 엔진(InnoDB)은 내부적으로 좀 더 복잡한 개념을 사용하는 것 같다.
추가적인 학습이 필요하다.

mydbops.wordpress.com

 

propagation

트랜잭션의 전파 옵션을 설정할 수 있다.

  • REQUIRED : 기본 속성. 부모 트랜잭션에 참여하며, 없으면 새로운 트랜잭션을 시작한다.
  • SUPPORTS : 부모 트랜잭션에 참여하며, 없으면 트랜잭션 없이 메소드를 수행한다.
  • MANDATORY :  부모 트랜잭션에 참여하며, 없으면 예외를 발생시킨다.
  • REQUIRES_NEW : 무조건 새로운 트랜잭션을 시작한다.
  • NOT_SUPPORTED : 트랜잭션에 참여하지 않는다. 부모 트랜잭션이 있다면 보류시킨다.
  • NEVER : 트랜잭션에 참여하지 않는다. 부모 트랜잭션이 있다면 예외를 발생시킨다.
  • NESTED : 진행중인 트랜잭션이 있다면 중첩 트랜잭션을 시작한다.

REQUIRES_NEW는 독립적인 트랜잭션을 시작하므로, 서로에게 영향을 주지 않는다. NESTED는 외부 트랜잭션의 결과가 내부 트랜잭션에 영향을 주지만, 내부 트랜잭션이 외부에 영향을 끼칠 수는 없다. 서브 트랜잭션을 시작할 때 SAVEPOINT라는 것을 기록한다고 한다.

외부트랜잭션=중요로직 / 내부트랜잭션=부가로직(ex. 로그INSERT)와 같은 상황에서 NESTED를 활용할 수 있다. 다만 사용하는 트랜잭션 매니저에 따라 사용하지 못할 수도 있으므로 이 부분을 유의하자.

 

readOnly

트랜잭션을 읽기 전용으로 설정한다. 트랜잭션 매니저에게 성능을 최적화할 수 있는 힌트를 제공하는 옵션이다. 일부 트랜잭션 매니저는 이를 지원하지 않는다고 한다.

  • DB에서 shared lock / exclusive lock을 구분하는 경우 쓰기 작업 방지 가능
  • 하이버네이트를 사용하는 경우 FlushMode.Manual로 변경하여 플러시가 작동하지 않음 (Dirty checking을 위한 스냅샷 비교나 엔티티 변경과 같은 작업 X)

 

 

참고자료

어노테이션 하나를 학습하는 데도 많은 지식이 필요하다 보니 글이 이렇게 길어졌네요. 아직 이해하지 못한 부분이 많은데도 말이에요. 나중에 좀 더 깊은 개념을 공부해서 심화 편도 작성해보겠습니다.

 

Database transaction - Wikipedia

A database transaction symbolizes a unit of work performed within a database management system (or similar system) against a database, and treated in a coherent and reliable way independent of other transactions. A transaction generally represents any chan

en.wikipedia.org

 

Transactional (Spring Framework 5.3.7 API)

Defines zero (0) or more exception names (for exceptions which must be a subclass of Throwable), indicating which exception types must cause a transaction rollback. This can be a substring of a fully qualified class name, with no wildcard support at presen

docs.spring.io

 

Transactional 어노테이션을 이용한 선언적 트랜잭션 - [종료]구루비 Dev 스터디 - 개발자, DBA가 함께

wiki.gurubee.net

 

[Spring 레퍼런스] 11장 트랜잭션 관리 #2 :: Outsider's Dev Story

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.11.5.4 다

blog.outsider.ne.kr

 

격리 수준

MySQL이 지원하는 네 가지 격리 수준을 소개한다.

hleee.medium.com

 

Lock으로 이해하는 Transaction의 Isolation Level

개요 내게 transaction의 isolation level은 개발할 때 항상 큰 찝찝함을 남기게 하는 요소였다. row를 읽기만 할 때는 REPEATABLE READ로, row를 삽입 / 수정 / 삭제할 때는 SERIALIZABLE로 isolation level을 지정했지

suhwan.dev

 

댓글