코딩마을방범대
[Java] @Transactional 에 대해서 (& 프록시객체) 본문
@Configuration, XML 기반으로도 트랜잭션을 구성할 수 있지만 이번엔 어노테이션을 이용한 방법으로 알아볼 것이다.
어노테이션 방식은 선언적 트랜잭션이라고 불리고, 선언 시 트랜잭션 기능이 적용된 프록시 객체가 생성된다.
프록시 객체란 앞서 포스팅했던 '프록시(Proxy)란' 과 비슷한 개념을 갖고 있다.
객체와 DB 사이의 중간 다리 역할을 해주는 또 다른 객체라고 볼 수 있다.
만약 사용자가 객체의 get을 이용해 데이터를 조회했을 때, 프록시 객체의 Entity Target 값이 비어있다면
DB를 조회해 Entity 생성 후 프록시 객체의 타겟 값으로 저장한다.
이 프록시 객체의 타겟값을 이용해 데이터 조회에 대한 응답을 리턴한다.
(JPA에서 지연로딩을 적용하면 프록시 객체가 생성된다.)
트랜잭션(Transaction)이란
데이터베이스의 상태를 변화시키기 위해 수행하는 작업의 단위
( SELECT, INSERT, UPDATE, DELETE )
트랜잭션의 목적
- 오류로부터 복구를 허용하고 데이터베이스를 일관성있게 유지하는 안정적인 작업 단위를 제공한다.
- 동시 접근하는 여러 프로그램 간 격리를 제공한다.
Transaction의 ACID
원자성(Atomicity)
- 한 트랜잭션 내에서 실행한 작업들은 하나의 단위로 처리
- 모두 성공하거나 모두 실패 (중간 단위까지 성공하고 실패하는 경우를 방지)
일관성(Consistency)
- 일관성있는 데이터베이스 상태를 유지
( 트랜잭션이 성공적으로 완료하면 언제나 동일한 데이터베이스 상태로 유지하는 것을 의미 )
독립성(Isolation)
- 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않도록 격리
( 트랜잭션 밖에 있는 어떤 연산도 중간 단계의 데이터를 볼 수 없음을 의미 )
영속성(Durability)
- 성공적으로 수행된 트래잭션은 영원히 반영(기록)되어야 함을 의미
( 데이터베이스 내의 데이터는 트랜잭션의 결과로만 변경되어야 하며, 외부 영향에 의해 변경될 수 없어야 한다. )
@Transactional 어노테이션 사용법
트랜잭션은 PlatformTransactionManager를 사용하여 트랜잭션을 시작하고, 정상 여부에 따라 Commit/Rollback을 수행한다.
간단하게, 트랜잭션 처리를 JDK Dynamic Proxy 객체에게 대신 위임하여 AOP로 동작하게 된다.
JDK Dynamic Proxy
Interface를 기반으로 Proxy를 생성해주는 방식이다.
직접 생성 시 Java.lang.reflect.Proxy 클래스의 newProxyInstance() 메소드를 이용해 프록시 객체를 생성한다.
※ 어노테이션을 통해 트랜잭션을 설정해줄 때 아래와 같은 옵션을 설정할 수 있다.
옵션 | 설명 |
isolation | 트랜잭션에서 일관성없는 데이터 허용 수준을 설정 (격리 수준) |
propagation | 동작 도중 다른 트랜잭션을 호출할 때, 어떻게 할 것인지 지정하는 옵션 (전파 옵션) |
noRollbackFor | 특정 예외 발생 시 rollback이 동작하지 않도록 설정 |
rollbackFor | 특정 예외 발생 시 rollback이 동작하도록 설정 |
timeout | 지정한 시간 내에 메소드 수행이 완료되지 않으면 rollback이 동작하도록 설정 |
readOnly | 트랜잭션을 읽기 전용으로 설정 |
isolation
일관성 없는 데이터의 부작용
Dirty read | 동시 트랜잭션의 커밋되지 않은 변경 내용을 조회하는 상황 (데이터 불일치) |
Nonrepeatable read | 동시 트랜잭션이 동일한 행을 업데이트하고 커밋하는 경우, 행을 다시 조회할 때 다른 값을 얻는 상황 |
Phantom read | 다른 트랜잭션이 특정 범위의 행을 추가/제거할 경우, 커밋 전/후 조회 결과가 다른 상황 |
@Transactional(isolation = Isolation.XXX)
public void example(String message) {
// ...
}
※ isolation을 이용해 설정할 수 있는 리스트
DEFAULT | - DBMS의 기본 격리 수준 적용 |
READ_UNCOMMITED (level 0) | - 트랜잭션의 동시 액세스 허용 - 세 가지 동시성 부작용이 모두 발생 (Dirty read, Nonrepeatable read, Phantom read) - Postgres는 미지원(대신 READ_COMMITED 로 폴백), Oracle은 지원하거나 허용하지 않음 |
READ_COMMITED (level 1) | - Dirty read 방지 - 나머지 부작용은 여전히 발생할 수 있음 (Nonrepeatable read, Phantom read) - Postgres, SQL Server 및 Oracle의 기본 수준 |
REPEATEABLE_READ (level 2) | - Dirty read, Nonrepeatable read 방지 - 업데이트 손실을 방지하기 위해 필요한 가장 낮은 수준 (동시 액세스를 허용하지 않음) - Phantom read 부작용은 여전히 발생 - MySQL의 기본 수준, Oracle은 미지원 |
SERIALIZABLE (level 3) | - 가장 높은 격리 수준이지만, 동시 호출을 순차적으로 실행하므로 성능 저하의 우려 - 모든 부작용을 방지 |
propagation
@Transactional(propagation = Propagation.XXX)
public void example(String user) {
// ...
}
※ propagation을 이용해 설정할 수 있는 리스트
REQUIRED (default) | - 활성 트랜잭션이 있는지 확인하고, 아무것도 없으면 새 트랜잭션을 생성 |
SUPPORTS | - 활성 트랜잭션이 있는지 확인하고, 있으면 기존 트랜잭션 사용, 없으면 트랜잭션 없이 실행 |
MANDATORY | - 활성 트랜잭션이 있으면 사용하고, 없으면 예외 발생 - 독립적으로 트랜잭션을 진행하면 안 되는 경우 사용 |
NEVER | - 활성 트랜잭션이 있으면 예외 발생 - 트랜잭션을 사용하지 않도록 제어할 경우 |
NOT_SUPPORTED | - 현재 트랜잭션이 존재하면 트랜잭션을 일시 중단한 다음 트랜잭션 없이 비즈니스 로직 실행 |
REQUIRES_NEW | - 현재 트랜잭션이 존재하는 경우, 현재 트랜잭션을 일시 중단하고 새 트랜잭션을 생성 |
NESTED | - 트랜잭션이 존재하는지 확인하고 존재하는 경우 저장점을 표시 - 비즈니스 로직 실행에서 예외가 발생하면 트랜잭션이 이 저장 지점으로 롤백 - 활성 트랜잭션이 없으면 REQUIRED 처럼 작동 |
rollbackFor, noRollbackFor
기본적으로 따로 설정을 하지 않을 경우 런타임 예외가 발생하면 롤백을 수행한다.
예외가 발생하지 않거나 체크 예외 발생 시 커밋을 수행하며, rollbackFor 옵션으로 기본 동작방식 변경이 가능하다.
rollbackFor
특정 예외가 발생 시 강제로 Rollback
@Transactional(rollbackFor=Exception.class)
noRollbackFor
특정 예외의 발생 시 Rollback 제외
@Transactional(noRollbackFor=Exception.class)
timeout
- 지정한 시간 내에 해당 메소드 실행이 완료되지 않은 경우 rollback 수행 (단위: second)
- -1일 경우 no timeout (default : -1)
@Transactional(timeout=10)
readOnly
- 트랜잭션을 읽기 전용으로 설정
- 성능을 최적화하기 위해 사용하거나, 특정 트랜잭션 작업 안에서 쓰기 작업이 일어나는 것을 의도적으로 방지하기 위해 사용
- readOnly = true인 경우 INSERT, UPDATE, DELETE 작업 진행 시 실행 시 예외 발생 (default : false)
@Transactional(readOnly = true)
참고사이트
[Spring] @Transactional 잘 사용해보기
'💡 백엔드 > Java' 카테고리의 다른 글
[Java] HttpURLConnection 통해서 사이트 접속 후 응답받기 (1) | 2023.10.04 |
---|---|
Java Quartz를 이용해 동적 스케줄링 설정하기 (0) | 2023.10.04 |
순차 & 병렬 & 병행 처리의 차이점과 Java의 stream & parallelStream (0) | 2023.08.01 |
[Java] Hashtable, HashMap, ConcurrentHashMap 에 대해서 (0) | 2023.07.31 |
[Java] 컬렉션 팩토리(Set, Map, List) (0) | 2023.07.31 |