공부한것들을 정리하는 블로그 입니다.
스프링에서의 트랜잭션 관리 본문
스프링에서의 트랜잭션 관리
- 스프링에서의 트랜잭션 관리
ex) 은행 계좌이체
1. Global Transactions
- 분산 트랜잭션(distributed transaction) 처리 : 서로 다른 데이터베이스 간에 트랜잭션이 발생
2. Local Transactions
- 로컬 트랜잭션은 간단한 JDBC 연결과 같은 단일 RDBMS와 애플리케이션 사이에서 발생한다.
- 개발자의 영역
- global과 local 트랜잭션 모두, 개발자가 스스로 다뤄야하는 영역이다.
- ex) jdbc를 사용한다면 트랜잭션 관리 API는 JDBC, Hibernate를 사용한다면 어플리케이션 서버의 hibernate 트랜잭션 API 등등
- 스프링 프레임워크와 트랜잭션 관리
- 스프링 프레임워크는 다양한 트랜잭션 API에 대한 추상화를 제공. 일관된 프로그래밍 모델을 제공하여 문제를 극복
- PlatformTransactionManager 인터페이스
- ex) 구현체
- JpaTransactionManager — JPA 트랜잭션 관리자
- DataSourceTransactionManager — JDBC 트랜잭션 관리자
- HibernateTransactionManager — Hibernate 트랜잭션 관리자이며 SessionFactory 와 바인딩된다
등등
- @Transactional 어노테이션
스프링의 트랜잭션은 프로그래밍 방식과 선언적 방식의 두가지 방식으로 구분할 수 있다.
- 프로그래밍 방식
스프링에서는 프로그래밍 방식으로 두가지를 제공하고 있다.
1. TransactionTemplate
2. 직접 PlatformTransactionManager 구현하기.
트랜잭션 관리가 비즈니스 로직과 함께 사용되기 때문에 프로그래밍 방식으로는 널리 사용되지는 않는다.
- 선언적 방식(@Transactional)
트랜잭션 관리가 비즈니스 로직과 별도로 분리되어 있기 때문에 널리 사용하고 있다.
선언전 트랜잭션에 경계를 설정할 수 있는 이유는 아래에서 설명할 프록시 객체 덕분이다.
트랜잭션은 대부분 그 성격이 비슷하기 때문에 적용 대상 별로 일일이 선언하지 않고 일괄적으로 설정하는 편이 좋다.
따라서, 특정 부가 기능을 임의의 타겟 객체에 부여할 수 있는 AOP가 주로 사용된다.
AOP 프록시 생성 과정에서 타겟 객체가 하나 이상의 인터페이스를 구현하고 있는 클래스라면 JDK Dynamic Proxy를 사용하고, 그렇지 않다면 CGLIB를 사용한다.
- 사용방법
- XML 혹은 Configuration 파일을 작성한다.
- 트랜잭션 어노테이션 설정을 로드
xml = <tx:annotation-driven transaction-manager="txManager"/>
java config = @EnableTransactionManagement
- DataSource와 TransactionManager를 정의한다.
- 이후 메소드 혹은 구현체 클래스 상단에 @Transactional 어노테이션을 사용한다. 클래스 수준에서 사용되는 경우 메소드 전체에 전역으로 적용된다.
- @Transactional 어노테이션 호출 원리
- Service 내에 @Transactional 어노테이션이 적용된 메서드가 존재할 경우
- 해당 Service가 다른 클래스에 주입되면 Spring은 내부적으로 proxy 클래스를 생성하여 대신 주입한다.
- proxy 패턴에 의해, 실제 호출되는 메소드는 프록시이며 내부적으로 target(본체)을 호출한다.
- 이때 트랜잭션 처리는 target(본체)의 전 후로 수행된다.
- @Transactional 경계
Spring AOP 방식 트랜잭션은 메소드 단위로 관리된다.
- 메소드가 끝날 때까지 커밋 또는 커넥션 반환이 이루어지지 않는다.
- 트랜잭션 대상 메소드 내에서 발생하는 SQL은 동일한 커넥션을 사용한다.
- 따라서 처리 시간이 긴 메소드의 경우에는 트랜잭션 단위를 조정해서 DB Lock 지속 시간이 지나치게 길어지거나 DB 커넥션 풀의 커넥션 개수가 모자라지 않도록 해야 한다.
- @Transactional 어노테이션 속성
1. propagation
트랜잭션 전파를 위한 설정이다. 트랜잭션 동작을 설정하는 데 매우 중요한 속성이다.
- REQUIRED (default) — 이미 진행 중인 트랜잭션이 없으면 새로 시작하고, 진행 중인 트랜잭션이 있다면 기존 트랜잭션에 참여한다.
- REQUIRES_NEW — 항상 새로운 트랜잭션을 시작한다.
- MANDATORY — 이미 진행 중인 트랜잭션이 없으면 예외를 발생시키고, 진행 중인 트랜잭션이 있다면 기존 트랜잭션에 참여한다.
- NESTED — 이미 진행 중인 트랜잭션(부모 트랜잭션)이 없으면 새로운 트랜잭션을 생성하고, 진행 중인 트랜잭션(부모 트랜잭션)이 있다면 중첩 트랜잭션을 만든다. (부모의 commit/rollback은 중첩에 영향을 주지만, 중첩은 부모에 영향 x)
- NEVER - 트랜잭션을 사용하지 않는다. 트랜잭션이 존재하면 예외를 발생시킨다.
- SUPPORTS — 진행 중인 트랜잭션이 있다면 기존 트랜잭션에 참여한다. 없을 경우 트랜잭션 없이 실행한다.
예시)
트랜잭션 전파는 임의의 한 트랜잭션의 경계에서 이미 진행 중인 트랜잭션이 존재할 때, 혹은 존재하지 않을 때 동작 방식을 결정하는 설정이다.
A라는 트랜잭션이 시작되고, 트랜잭션 A가 끝나지 않은 시점에서 트랜잭션 B 메소드가 호출된다고 가정해 보자.
그렇다면, B는 어느 트랜잭션에서 동작해야 할까? (B에 propagation이 적용되어 있을 경우)
*A 트랜잭션과 B 트랜잭션은 서로 다른 Service 레이어에 속해야 한다.
1) A와 B 전체가 롤백
- A라는 트랜잭션이 시작되었고 아직 진행 중이라면, B의 코드는 새로운 트랜잭션을 만들지 않고 A에서 시작된 트랜잭션에 참여할 수 있다. 이러한 경우, B를 호출한 b.method() 까지 마치고 이후 작업에서 예외가 발생한다면 A와 B가 모두 A의 트랜잭션에 하나로 묶여 있으므로 전체가 롤백될 것이다.
2) A만 롤백 or B만 롤백
- 트랜잭션 B가 트랜잭션 A와 별도의 트랜잭션을 만들 수 있다. 이 경우, 트랜잭션 B 경계를 빠져 나가는 순간, B 트랜잭션은 독립적으로 커밋되거나 롤백된다. 트랜잭션 A는 그 영향을 받지 않고 진행될 것이다. A의 (2)에서 예외가 발생하더라도 트랜잭션 A만 롤백되고, 트랜잭션 B에는 아무런 영향이 없을 것이다.
- REQUIRED (default) — A 하나의 트랜잭션만 존재 (기본적이고 가장 많이 사용)
- REQUIRES_NEW — A와 B, 2개의 트랜잭션이 존재 (독립적인 트랜잭션이 보장되어야 하는 코드에 적용)
- MANDATORY — A 하나의 트랜잭션만 존재, 그렇지 않을 경우 예외를 발생 (독립적인 트랜잭션을 생성하면 안되는 경우에 사용)
- NESTED — A와 B, 2개의 트랜잭션이 존재. or (REQUIRED와 마찬가지로 부모 트랜잭션이 존재하지 않으면 독립적으로 트랜잭션을 생성해서 사용한다.)
- NEVER - 트랜잭션 존재 x. 트랜잭션이 존재하면 예외를 발생시킨다
- SUPPORTS — A 하나의 트랜잭션만 존재
2. isolation
트랜잭션 격리 수준. 트랜잭션이 다른 트랜잭션과 격리되어야 하는 수준을 결정한다.
- 트랜잭션이란,
트랜잭션이란, 데이터베이스의 데이터를 조작하는 작업의 단위(unit of work)이다.
트랜잭션은 흔히 이론적으로 ACID 원칙을 보장해야 한다고 한다.
하지만, 실제로는 ACID 원칙은 종종 지켜지지 않는다. 왜냐하면 ACID 원칙을 strict 하게 지키려면 동시성이 매우 떨어지기 때문이다.
그렇기 때문에 DB 엔진은 ACID 원칙을 희생하여 동시성을 얻을 수 있는 방법을 제공한다. 바로 트랜잭션의 isolation level이다.
isolation이 낮을수록 ACID 원칙이 지켜지지 않기에(격리성이 낮음) 문제가 생길 수 있지만, 대신 더 높은 동시성을 얻을 수 있다.
데이터베이스에서 트랜잭션이 독립적인 수행을 하도록 하기 위해서, isolation level에 따라 서로 다른 Locking을 사용한다.
isolation level이 높아질수록 더 많이, 더 빡빡하게 lock을 거는 것이다.
따라서, isolation level 별 locking 전략을 이해하기 위해, 먼저 필수적인 Lock들에 대한 개념들을 먼저 정리하고 넘어간다.
- MYSQL의 innoDB의 lock
- Lock : 트랜잭션 처리의 순차성을 보장하기 위한 방법이다.(동시성 제어)
- Locking으로 동시에 수행되는 수많은 트랜잭션들을 순서대로 처리하는 방식으로 구현하게 되면, 데이터베이스의 성능은 떨어지게 된다.
- 성능을 높이기 위해 Locking의 범위를 줄인다면, 잘못된 값이 처리될 문제가 발생할 수 있다.
- Row-level lock
- shared lock (S lock)
- 공유 락. read a row
- 데이터를 읽을 때 사용되어지는 lock
- 일반적인 SELECT 쿼리는 lock을 사용하지 않고 DB를 읽어 들인다.
- 하지만 SELECT ... FOR SHARE 등 일부 SELECT 쿼리는 read 작업을 수행할 때 InnoDB가 각 row에 S lock을 건다.
- 여러 transaction이 동시에 한 row에 S lock을 걸 수 있다.
- 즉, 여러 transaction이 동시에 한 row를 읽을 수 있다.
- S lock이 걸려있는 row에 다른 transaction이 X lock을 걸 수 없다.
- 즉, 다른 transaction이 읽고 있는 row를 수정하거나 삭제할 수 없다.
- Exclusive lock (X lock)
- 배타 락. update or delete a row
- 데이터를 변경하고자 할 때 사용되며, 트랜잭션이 완료될 때까지 유지된다.
- SELECT ... FOR UPDATE나 UPDATE, DELETE 등의 수정 쿼리를 날릴 때 각 row에 걸리는 lock이다.
- X lock이 걸려있는 row에는 다른 transaction이 S lock과 X lock 둘 다 걸 수 없다.
- 즉, 다른 transaction이 수정하거나 삭제하고 있는 row는 읽기, 수정, 삭제가 전부 불가능하다.
- Record lock
- Record lock은 row가 아니라 DB의 개별 index record에 걸리는 lock이다.
- 여기도 row-level lock과 마찬가지로 S lock과 X lock이 있다.
- 테이블에 인덱스가 생성되어 있지 않더라도 테이블 생성시에 함께 생성되는 default Clustered Index의 레코드에 Lock을 걸어, 항상 인덱스 레코드에 Lock을 설정한다.
- ex) 트랜잭션 A가 테이블에 대해 id=1 조건으로 조회중일 경우, 트랜잭션 B의 id=1 Update/Delete 접근을 막는다.
- Gap lock
- Gap lock은 DB index record의 gap에 걸리는 lock이다.
- ex) index가 id인 table에서 id=3, id=10인 row가 2개 있으면, gap=4~9이다.
- Gap lock은 해당 gap에 접근하려는 다른 쿼리의 접근을 막는다.
- Record lock이 해당 index를 타려는 다른 쿼리의 접근을 막는 것과 동일하다.
- record lock이 이미 존재하는 row가 변경되지 않도록 보호하는 반면, gap lock은 조건에 해당하는 새로운 row가 추가되는 것을 방지하기 위함
- ex) between 3 and 10으로 조회중이면, 2~11을 입력하는 것을 lock.
- phantom row 현상을 막는 것이 목적이므로, 최초 레코드의 이전 gap + 마지막 레코드의 이후의 gap 에도 lock을 설정한다.
- next-key lock
- gap lock + record lock의 조합으로 만들어진다.
- ex) index가 id인 table에서 id=3, id=10인 row가 2개 있으면, next-key lock은 2~11에 걸린다.
* Dirty Read : 변경 후 아직 commit 되지 않은 값을 읽고, Rollback 된 후의 값을 다시 읽어 최종 결과 값이 상이한 현상
* Non-Repeatable Read : 한 트랜잭션 내에서 같은 쿼리를 두번 수행할 때, 그 사이에 다른 트랜잭션이 값을 수정 또는 삭제함으로써 두 쿼리가 상이하게 나타나는, 비 일관성의 발생을 의미한다.
* phantom row
Phantom Row의 "Phatom"은 유령이라는 뜻으로, 트랜잭션 A가 수행중 다른 트랜잭션 B가 생성한 Row가 갑자기 나타나는 것을 말한다
isolation 종류
* isolation level이 낮을수록 ACID 원칙이 지켜지지 않기에(격리성이 낮음) 문제가 생길 수 있지만, 대신 더 높은 동시성을 얻을 수 있다.
* '동시성 제어'란 동시에 실행되는 트랜잭션의 수를 최대화 하는 것과 데이터 CRUD 시 데이터의 무결성을 유지하는 것을 이야기합니다.
- DEFAULT
- 데이터 소스의 기본 격리 수준
- Oracle 은 (level 1) READ_COMMITED
- Mysql InnoDB 는 (level 2) REPEATABLE_READ
- 0 : READ_UNCOMMITTED
- Dirty Read, Non-Repeatable Read 및 Phantom Read가 발생할 수 있음을 나타낸다.
- 다른 트랜잭션의 커밋되지 않은 데이터도 읽을 수 있음.
- 1 : READ_COMMITTED
- Dirty Read를 방지하고 반복할 수 없으며 Phantom Read가 발생할 수 있음을 나타낸다.
- 커밋된 데이터만 읽음. 반복조회시 커밋 시점에 따라 데이터 상이.
- 2 : REPEATABLE_READ
- Dirty Read와 Non-Repeatable Read가 방지되지만 Phantom Read가 발생할 수 있음을 나타낸다.
- 반복적으로 조회하여도 동일한 데이터를 보장.
- 3 : SERIALIZABLE
- Dirty Read와 Non-Repeatable Read, Phantom Read가 방지될 수 있음을 나타낸다.
- 데이터 처리의 직렬화를 보장.
- 데이터베이스에서 거의 사용되지 않음
3. readOnly : 트랜잭션이 읽기 전용인지 또는 읽기/쓰기인지 여부
4. timeout : 트랜잭션 타임아웃(처리 시간초과)
5. rollback
- rollbackFor : 트랜잭션의 롤백을 발생시켜야 하는 예외(Exception) 클래스의 배열
- rollbackForClassName : 트랜잭션의 롤백을 발생시켜야 하는 예외 클래스 이름의 배열
- noRollbackFor : 트랜잭션 롤백을 유발하지 않아야 하는 예외 클래스 개체의 배열
- noRollbackForClassName : 트랜잭션 롤백을 유발하지 않아야 하는 예외 클래스 이름의 배열
https://sas-study.tistory.com/443
https://steady-coding.tistory.com/610
'Spring > 공부' 카테고리의 다른 글
[인프런 김영한 로드맵]스프링 입문 강의 정리 (0) | 2022.12.23 |
---|---|
@Transactional isolation (0) | 2022.10.27 |
@Transactional propagation (0) | 2022.10.27 |
Spring Profile 운영/개발/로컬 (0) | 2022.09.13 |
토비의스프링3.1 2권 3장. 스프링 웹 기술과 스프링 MVC (0) | 2022.08.05 |
토비 2권 요약 참고용 (0) | 2022.08.05 |
토비의스프링3.1 2권 1장. IoC 컨테이너와 DI (0) | 2022.08.02 |
토비의스프링3.1 9장. 스프링 프로젝트 시작하기 (0) | 2022.08.02 |