티스토리 뷰
TIL
@Transaction 어노테이션을 동작원리에 대해 공부하고 추가적으로 ACID에 대해 공부하고자 합니다.
우테코의 테크톡 영상을 기반으로 정리하였습니다!
https://www.youtube.com/watch?v=aX9c7z9l_u8&t=188s
트랜잭션
트랜잭션이란 하나의 단위로 처리되길 바라는 쿼리문의 묶음이라고 할 수 있다. 일련의 업무는 절대 분리되어서는 안되고 일부만 실행되어서도 안된다. 즉, 절대로 깨져서는 안되는 하나의 작업을 트랜잭션이라고 한다.
ex) 은행업무에서 A사용자가 B사용자에게 돈을 이체시 중간에 작업이 중단되면 안된다. 중단이 된다면 Rollback이 되던가 아니면 일련의 업무를 모두 수행하고 Commit이 되어야 한다. 이렇게 이체작업에서 이체 과정 전체를 트랜잭션이라고 할 수 있다.
트랜잭션 4가지 특성 (ACID)
ACID
트랜잭션의 4가지 특성입니다. 원자성, 일관성, 독립성, 지속성
1. 원자성 (Atomicity)
하나가 전부 실행되든, 실패하든 일부만 실행되는 경우는 없다는 것이 원자성이다. 여러 명령을 실행 시에 하나라도 실패했다면 어떠한 작업을 하기 이전인 상태로 되돌려야 하며 이를 Rollback이라고 한다. 반대로 일련의 동작과정을 모두 성공적으로 수행했다면 수정된 내용을 데이터베이스에 반영한다. 이를 트랜잭션 Commit이라고 한다. 즉, Rollback이나 Commit이 수행되어야 트랜잭션이 종료되는 것이다.
2. 일관성 (Consistency)
데이터베이스의 상태, 데이터베이스 내의 계층 관계, 칼럼의 속성 등이 항상 일관되게 유지해야 한다는 일관성이 있다. 예를 들어 어떠한 컬럼의 속성이 수정되었다면 Trigger를 통해 일괄적으로 모든 데이터베이스에 적용해야 한다.
3. 지속성 (Durability)
트랜잭션이 성공적으로 수행되어 커밋되었다면 어떠한 문제가 발생하더라도 데이터베이스에 그 내용이 영원히 지속되어야 한다는 것이 지속성이다. 이를 위해 모든 트랜잭션은 로그로 남겨져 어떠한 장애에도 대비할수 있게 해야 한다.
4. 독립성 (Isolation)
각 트랜잭션은 독립적으로 수행해야 하는 것이 독립성이다. 예를 들어 A와 B가 C에게 동시에 만원씩 송금할 때, 순차적으로 진행되면 C는 잔액이 2만원이 되어야 하지만 이체전 잔액 조회시 0원으로 조회되고 이체 연산 후 잔액이 만원이 될 수도 있다. 트랜잭션은 격리 수준 설정을 통해 독립성을 보장할 수 있다. 하지만 모든 작업의 독립성을 보장해 하나씩 순차적으로 진행하게 된다면 cpu는 dbms보다 인풋, 아웃풋 작업을 빈번히 수행하기 때문에 트랜잭션을 순차적으로 수행하면 cpu는 점점 응답을 기다리는 시간이 길어져 프로그램이 비효율적으로 동작하는 문제가 발생할 수 있다. 이처럼 데이터베이스에 저장된 데이터의 무결성과 동시성 성능을 지키기 위해 트랜잭션의 설정이 중요하다.
(Isolation Level : https://joinwithyou.tistory.com/86 )
스프링의 트랜잭션
데이터베이스에서는 각각의 명령을 하나의 트랜잭션으로 보고 독립성을 보장해 주기때문에 여러 명령을 하나의 트랜잭션으로 묶고 싶은 경우 개발자가 직접 트랜잭션의 경계설정을 통해 트랜잭션을 명시하는 일이 필요하다. Spring에서는 이러한 경계설정을 결정하여 데이터베이스에 전달할 수 있다.
Q. 스프링은 트랜잭션을 어떻게 지원할까?
스프링에서는 트랜잭션 추상화 인터페이스인 PlatformTransactionManager를 제공하여 다양한 DataSource에 맞게 트랜잭션을 관리할 수 있다. PlatformTransactionManager은 getTransaction, rollback, commit으로 구성되어 있다. getTransactiond을 통해 파라미터로 전돨되는 TransactionDefinition에 따라 트랜잭션을 시작한다. 트랜잭션을 문제없이 수행하면 commit을 하고 문제가 발생하면 rollback을 한다. getTransaction부터 커밋과 롤백을 하는 동작까지가 트랜잭션의 경계설정이다.
Q. 스프링이 제공하는 다양한 트랜잭션 매니저 구현체는 무엇이 있을까?
대표적으로 DataSourceTransactionManager, JpaTransactionManager, JtaTransactionManger가 있다. DataSourceTransactionManager는 JDBC에 JpaTransactionManager는 JPA에 사용되는 매니저이다. 이 두 매니저는 하나의 데이터베이스를 사용하거나 각각의 데이터를 독립적으로 사용하는 로컬 트랜잭션의 경우에 사용할 수 있다. 하나 이상의 데이터베이스가 참여하는 경우라면 글로벌 트랜잭션에 사용되는 JtaTransactionManger를 사용할 수 있다. 여러 개의 데이터베이스에 대한 작업을 하나의 트랜잭션으로 묶을 수 있고, 다른 서버에 분산된 것을 묶을 수 있다. 하지만 이렇게 코드를 통해 직접적으로 구현하는 방식 이외에도 스프링의 AOP를 이용한 선언적 트랜잭션을 제공하고 있다.
1. 선언적 트랜잭션 - tx namespace
선언적 트랜잭션은 tx namespace를 사용하는 방안과 어노테이션을 기반으로 설정하는 방안이 있다. tx namespace를 사용하는 방식은 Bean 설정 파일에서 트랜잭션 매니저를 등 록하고 속성과 대상을 정의해 트랜잭션을 적용하겠다고 명시하는 것이다. 이렇게 사용하면 코드에 영향을 주지 않고 일괄적으로 트랜잭션을 적용하고 변경할 수 있다는 장점이 있다.
2. 선언적 트랜잭션 - @Transaction
@Transaction은 어노테이션 기반으로 트랜잭션을 설정하는 방안이다. 트랜잭션 어노테이션은 메서드, 클래스, 인터페이스 등에 적용할 수 있다. 클래스 상단에 적용되는 어노테이션에 대해서는 해당 클래스에 존재하는 모든 메서드에 적용된다. 트랜잭션 어노테이션이 중첩되어 존재하는 경우에는 클래스 메서드,클래스, 인터페이스 메서드, 인터페이스 순으로 우선순위가 적용된다.
어노테이션이 적용된 메서드는 메서드 시작부터 트랜잭션이 시작되고, 메서드를 성공적으로 끝마치면 트랜잭션 커밋, 도중에 문제가 발생하면 롤백하는 과정이 진행된다. 어노테이션은 데이터베이스에 여러번 접근하면서 하나의 작업을 수행하는 서비스 계층 메서드에 붙이는 것이 통상적이다. 코드에 일일이 붙이기 번거롭고 쉽게 놓칠 수 있다는 단점이 있지만 보다 세밀한 설정을 손쉽고 간편히 할 수 있다는 장점이 있다.
@Transaction 속성
[ # propagation ]
propagation은 트랜잭션 전파이다. 트랜잭션 전파란 트랜잭션 경계에서 이미 진행 중인 트랜잭션이 있을 때 어떻게 동작할지 결정하는 것이다.
1. REQUIRED
Default 설정인 REQUIRED에서는 A메서드가 진행중인 트랜잭션이 없기 때문에 새로 트랜잭션1로 시작하고 B메서드는 이미 시작된 트랜잭션이 있기 때문에 트랜잭션1에 같이 참여한다. 두 메서드가 하나의 트랜잭션으로 실행되기 때문에 어느 메서드에서 문제가 발생해도 실행했던 모든 메서드가 롤백된다.
2. SUPPORTS
SUPPORTS는 진행중인 트랜잭션이 있으면 REQUIRED 처럼 참여하고 트랜잭션이 없으면 트랜잭션 없이 메서드를 실행한다.
3. MANDATORY
설정이 된 메서드는 진행중인 트랜잭션이 있으면 참여하고 없으면 예외가 발생한다. 혼자서 트랜잭션을 시작할 수 없고 메서드를 실행할 수도 없다.
4, REQUIREDD_NEW
REQUIREDD_NEW는 항상 새로운 트랜잭션을 시작한다. 때문에 A메서드는 트랜잭션1이 되고 새로운 메서드B가 시작되었을 때 진행중인 트랜잭션인 트랜잭션1을 잠시 보류시키고 자신의 메서드인 메서드B를 트랜잭션으로 실행한다.
5. NOT_SUPPORTED
NOT_SUPPORTED는 이미 시작된 트랜잭션이 있으면 보류하고 자신의 메서드를 실행하는, 트랜잭션을 사용하지 않는 설정이다. B메서드를 실행하는데 트랜잭션1이 존재한다면 트랜잭션1은 보류하고 B메서드를 실행한다.
6. NEVER
NEVER는 트랜잭션을 사용하지 않도록 강제한다. 이미 진행 중인 트랜잭션이 없다면 자신의 메서드를 실행하지만 실행중인 트랜잭션이 있따면 예외가 발생한다.
7. NESTED
이미 진행 중인 트랜잭션이 있으면 그 안에 새로운 트랜잭션을 만드는 설정이다. 트랜잭션1이 실행중인데 B메서드가 시작되면 트랜잭션1 내부에 B메서드를 트랜잭션2로 삽입한다. 중첩된 트랜잭션2는 부모인 트랜잭셔1의 커밋, 롤백에는 영향을 받지만 트랜잭션2의 커밋과 롤백에는 트랜잭션1이 영향을 받지 않는다.
[ # Isolation ]
Isolation은 트랜잭션 격리수준이다. 동시에 여러 트랜잭션이 실행될 때, 트랜잭션의 작업 내역을 다른 트랜잭션에게 보여줄지 말지 결정하는 것으로 가능한 많은 트랜잭션을 동시에 진행하면서도 문제가 생기지 않도록 하려는 설정이다.
1. READ_UNCOMMITTED
READ_UNCOMMITTED는 가장 낮은 격리수준으로 아직 커밋되지 않은 데이터를 다른 트랜잭션이 읽을 수 있는 설정이다. 트랜잭션1이 진행중에 A+1의 작업을 트랜잭션2가 읽어왔는데 트랜잭션1에서 다시 A+100 실행 중 문제가 발생해 롤백된다면 트랜잭션2를 실행도중 읽어온 트랜잭션1의 A+1의 데이터는 존재하지 않는 데이터를 읽어오게 되는 것이다.
2. READ_COMMITTED
READ_UNCOMMITTED의 문제를 해결한 것이 READ_COMMITTED이다. 두번째로 낮은 격리수준으로 커밋되지 않은 정보는 읽을 수 없다. 트랜잭션1에서 작업이 완료되면 커밋되어야 트랜잭션2는 트랜잭션1의 A정보를 읽어올 수 있다. 하지만 트랜잭션2가 읽은 A정보를 또 다른 트랜잭션3이 수정할 수 있어 트랜잭션2가 다시 읽으면 값이 다르다는 문제가 발생한다.
3. REPEATABLE_READ
READ_COMMITTED의 문제를 REPEATABLE_READ가 수정해 준다. 세번째로 낮은 격리수준으로 MySQL의 디폴트 설정이다. 하나의 트랜잭션이 읽은 로우를 다른 트랜잭션이 수정할 수 없게 하였다. 하지만 기존 데이터베이스에 '하늘'정보를 트랜잭션1이 조회하여 '하늘'이라는 로우를 얻었는데 트랜잭션2가 '바다'라는 새로운 로우를 추가하는 것은 제한되지 않아 다시 발견했을때 발견하지 못한 새로운 로우가 발견될 수 있다는 문제가 있다.
4, SERIALIZABLE
SERIALIZABLE은 가장 강력한 격리수준으로 이 속성을 설정할 경우 동시에 같은 테이블의 정보를 접근할 수 없다. 하지만 트랜잭션을 순차적으로 수행하는 것과 다를 바 없어 성능이 떨어지기 때문에 사용을 지양하는 것이 좋다.
[ # Timeout ]
어노테이션이 달린 메소드를 실행하는데 설정한 시간이 지나면 예외가 발생해 롤백된다. 디폴트값은 설정되어 있지 않다.
[ # ReadOnly ]
ReadOnly는 읽기 전용 트랜잭션이다. ReadOnly를 True로 설정하면 UPDATE, INSERT, DELETE 작업이 일어나는 것을 방지 할 수 있다. 추가로 flush모드가 manual로 설정되어 jpa의 더티체킹 기능을 무시할 수 있다고 한다. 또한 ReadOnly의 디폴트 값은 false로 모든 작업을 허용한다.
'Spring' 카테고리의 다른 글
Mybatis Multi ResultSet (다중 select 쿼리 결과 , 네트워크 한 번에 가져오기) (0) | 2023.05.27 |
---|---|
JPA 동작 원리 (feat. 자바 ORM표준 JPA 프로그래밍) (0) | 2021.10.23 |
[망나니 개발자] Dispatcher-Servlet이란? (0) | 2021.06.08 |
[망나니 개발자] @Bean, @Configuration, @Component 어노테이션 (0) | 2021.06.08 |
[망나니 개발자] Bean Scope(빈 스코프)의 종류 (0) | 2021.06.08 |