Programming/SpringBoot

[Spring] Transaction

owls 2024. 6. 20. 10:00
728x90

트랜잭션

더 이상 쪼갤 수 없는 최소 단위의 작업

트랜잭션 경계 안에서 진행된 작업은 모두 성공하던지 모두 취소돼야 함

 

트랜잭션 속성

● 트랜잭션 전파 Propagation

트랜잭션 경계에서 이미 진행 중인 트랜잭션이 있을 때 또는 없을 때 어떻게 동작할 것인가를 결정하는 방식

PROPAGATION_REQUIRED

가장 많이 사용

진행 중인 트랜잭션이 없으면 새로 시작하고 이미 시작된 트랜잭션이 있으면 이에 참여함

 

PROPAGATION_REQUIRES_NEW

항상 새로운 트랜잭션을 시작한다.

독립적인 트랜잭션이 보장돼야 하는 코드에 적용할 수 있다.

 

PROPAGATION_NOT_SUPPORTED

트랜잭션 없이 동작하도록 만들 수 있다.

진행 중인 트랜잭션이 있어도 무시한다.

트랜재션 경계설정은 보통 AOP를 이용해 한번에 많은 메소드에 동시에 적용하는 방법을 사용함.

근데 특정한 메소드만 제외하려면 포인트컷이 상당히 복잡해짐

 

● 격리 수준 Isolation level

모든 DB트랜잭션은 격리수준을 가지고 있어야 한다.

서버 환경에서는 여러 개의 트랜잭션이 동시에 진행될 수 있다.

적절한 격리수준을 조정해서 가능한 많은 트랜잭션을 동시에 진행시키면서도 문제가 발생하지 않게 하는 제어가 필요.

격리수준은 기본적으로 DB에 설정되어 있지만, JDBC드라이버나 DataSource등에서 재설정할 수 있다.

MySQL의 기본 격리수준은 REPEATABLE_READ 이다.

격리수준 의미 dirty read 반복할 수 없는 읽기 팬텀 읽기
ISOLATION_DEFAULT   - - -
ISOLATION_READ_UNCOMMITED 커밋되지 않은 데이터 변경 사항을 읽을 수 있음 O O O
ISOLATION_READ_COMMITED 커밋된 데이터 변경 사항만 읽을 수 있음 X O O
ISOLATION_REPEATABLE_READ 같은 데이터 필드는 여러 번 반복해서 읽더라도 동일한 값을 읽도록 함.
자신이 변경한 필드는 변경된 값을 읽게 됨
X X O
ISOLATION_SERIALIZABLE 완전한 ACID를 보장하는 격리 수준.
성능은 가장 비효율
X X X

- Dirty Read

한 트랜잭션에서 다른 트랜잭션에 의해 변경되었지만 아직 커밋되지 않은 데이터를 읽게 되는 문제

이 데이터가 커밋되지 않고 롤백되어 버린다면, 첫 번째 트랜잭션에서 읽은 이 데이터는 유효하지 않은 데이터가 됨

 

- Nonrepeatable read

트랜잭션이 같은 질의를 두 번 이상 수행할 때 서로 다른 데이터를 얻게 되는 문제

보통 각 질의 수행 사이에 동시 진행 중인 다른 트랜잭션에서 이 데이터를 변경하는 경우에 발생함

 

- Phantom read

 어떤 트랜잭션(T1)이 둘 이상의 데이터 행을 읽은 다음, 동시 진행 중인 다른 트랜잭션(T2)이 추가 행을 삽입할 때 발생함.

T1에서 동일한 질의를 다시 수행하면, T2는 이전에 없던 데이터 행까지 읽음.

 

Timeout

제한 시간

트랜잭션을 수행하는 데 제한 시간 설정

 

Read only

읽기 전용

트랜잭션 내에서 데이터를 조작하는 시도를 막음

 

트랜잭션을 적용할 타깃 클래스의 메소드는 모두 트랜잭션 적용 후보가 되는 것이 바람직함

트랜잭션용 포인트컷 표현식에는 메소드나 파라미터, 예외에 대한 페턴을 정의하지 않는게 바람직함

클래스보다는 인터페이스 타입을 기준으로 타입 패턴을 적용하는 것이 좋음

인터페이스는 클래스에 비해 변경 빈도가 적고 일정한 패턴을 유지하기 쉽기 때문

 

스프링의 트랜잭션 추상화

DAO에서 상관 없이, 트랜잭션 기술에 상관 없이 DAO에서 일어나는 작업들을 하나의 트랜잭션으로 묶어서 추상 레벨에서 관리하게 해주는 트랜잭션 추상화가 없었다면 AOP를 통한 선언적 트랜잭션이나 트랜잭션 전파 등은 불가능했을 것이다.

 

스프링 트랜잭션 추상화의 핵심은 트랜잭션 매니저 트랜잭션 동기화

PlatformTransactionManager인터페이스를 구현한 트랜잭션 매니저를 통해 구체적인 트랜잭션 기술의 종류에 상관없이 일관된 트랜잭션 제어가 가능

트랜잭션 동기화 기술이 있었기에 시작된 트랜잭션 정보 저장소에 보관해두었다가 DAO에서 공유할 수 있다.

 

트랜잭션 동기화 기술은 트랜잭션 전파를 위해서도 중요한 역할을 한다.

진행 중인 트랜잭션이 있는지 확인하고, 트랜잭션 전파 속성에 따라서 이에 참여할 수 있도록 만들어주는 것도 트랜잭션 동기화 기술 덕분임

 

트랜잭션 속성은 처음 트랜잭션이 시작될 때만 적용되고 그 이후에 참여하는 메소드의 속성은 무시된다.

 

JdbcTeamplate은 트랜잭션이 시작된 것이 있으면 그 트랜잭션에 자동으로 참여하고, 없으면 트랜잭션 없이 자동 커밋 모드로 Jdbc작업을 수행한다.

JdbcTemplate의 메소드 단위로 마치 트랜잭션 전파 속성이 REQUIRED인 것처럼 동작한다고 볼 수 있다.

 

트랜잭션 어노테이션

@Transactional

@Target({ElementType.METHOD, ElementType.TYPE}) // 어노테이션 대상, 메소드와 타입(클래스, 인터페이스)
@Retention(RetentionPolicy.RUNTIME) // 어노테이션 정보 유지, 런타임동안 유지
@Inherited //상속을 통해서도 어노테이션 정보 얻을 수 있게 함

스프링은 @Transactional어노테이션이 부여된 모든 오브젝트를 자동으로 타깃 오브젝트로 인식함

-> TransactionAttributeSourcePointcout 포인트컷이 사용됨

 

@Transactional어노테이션이 부여된 빈 오브젝트를 모두 찾아서 포인트컷의 선정 결과로 돌려줌

기본적으로 트랜잭션 속성을 정의하지만, 동시에 포인트컷의 자동등록에도 사용됨

 

=> 포인트 컷과 트랜잭션 속성을 어노테이션 하나로 지정할 수 있다!

 

@Transactional은 기본적으로 트랜잭션을 강제 롤백시키도록 설정되어 있다.

 

@Rollback

롤백 기능을 제어하는 어노테이션

기본값은 true

@Rollback(false) : 트랜잭션은 적용되지만 롤백은 하지 않는다.

메소드 레벨에만 적용 가능

 

@TransactionConfiguration

롤백에 대한 공통 속성을 지정할 수 있다

디폴트 롤백 속성은 false로 해두고, 테스트 메소드 중에서 일부만 롤백 적용하고 싶으면 메소드에 @Rollback을 부여하면 됨

@Rollback의 기본 값은 true이므로 이때는 트랜잭션을 롤백해줌

@TransactionConfiguration(defaultRollback=false)

 

@Transactional(propation = Papagation.NEVER)

테스트 메소드에 부여하면 클래스 레벨의 @Transactional 설정을 무시하고 트랜잭션을 시작하지 않은 채로 테스트를 진행한다.

@NotTransactional 이 스프링 3.0에서 제거 대상이 되어 트랜잭션 전파 속성을 사용하는 방법으로 사용해야 한다.

 

 

예제

격리 수준에 따라 데이터가 어떻게 보여지는지 테스트 해보자.

READ_UNCOMMITED  → READ_COMMITED → REPEATABLE_READ → SERIALIZABLE

순으로 격리 레벨이 높아진다.

 

트랜잭션 전파 속성을 Requires_new로 새로운 트랜잭션을 시작하여 독립적인 트랜잭션으로 동작하도록 설정하자.

product테이블의 price를 조회, 변경하는 중에 데이터에 접근하면 어떤 데이터가 나오는지 테스트해보자.

Case1 ) READ_UNCOMMIIED

커밋되지 않은 데이터 변경사항을 읽을 수 있습니다.

 

초기 price = 50000 이다.

 

 


Case2 ) READ_COMMIIED

 

 

 

 

 

 


Case 3 ) REPEATABLE_READ 

 

 

 


Case 4 ) SERIALIZABLE

 

 

 


 

 

 

 

참고

토비의 스프링 3.1

728x90