회사 코드를 보면서 Controller와 Service에 어떤 코드를 넣어야 하는가에 대한 의문이 생겼다.
Service에는 비즈니스 로직을 코딩하라고 하지만 비즈니스 로직도 어떤 크기에 따라 나눠야하는지에 대해 고민을 하다가 @Transactional에 대해 알게 되었다. Transaction 단위로 개발을 위해 @Transactional에 대해 공부하자
1. 원자성
트랜잭션 내에서 실행한 작업들은 하나로 간주한다. 즉, 모두 성공 또는 모두 실패
2. 일관성
트랜잭션은 일관성 있는 데이터베이스 상태를 유지한다.
3. 격리성
동시에 실행되는 트랜잭션들은 서로 영향을 미치지 않도록 격리해야한다.
4. 지속성
트랜잭션을 성공적으로 마치면 결과가 항상 저장되어야 한다.
Spring에서는 트랜잭션 처리를 지원하는데 @Transactional을 선언하여 사용하는 방법이 일반적이고, 이를 선언적 트랜잭션이라고 부른다.
트랜잭션에서 일관성이 없는 데이터를 허용하도록 하는 수준을 말한다.
DEFAULT : 기본 격리 수준
READ_UNCOMMITED (level 0)
- 커밋되지 않는 데이터에 대한 읽기를 허용
- 즉 어떤 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 B라는 아직 완료되지 않은 데이터를 읽을 수 있다.
Problem : Dirty Read 발생
READ_COMMITED (level 1)
- 트랜잭션이 커밋 된 확정 데이터만 읽기 허용
- 어떠한 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 해당 데이터에 접근할 수 없다.
Problem : Dirty Read 방지
REPEATABLE_READ (level 2)
- 트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정이 불가능하다.
- 선행 트랜잭션이 읽은 데이터는 트랜잭션이 종료될 때까지 후행 트랜잭션이 갱신하거나 삭제가 불가능하기 때문에 같은 데이터를 두번 쿼리할 때 일관성 있는 결과를 리턴한다.
Problem : Non-Repeatable Read 방지
shared lock (공유 잠금) 이란?
읽기 잠금이라고도 불린다. 리소스를 다른 사용자가 동시에 읽을 수 있게 하지만 변경은 불가능하게 하는 것이다.
Exclusive lock 과 Shared lock의 차이
SERIALIZABLE (level 3)
- 데이터의 일관성 및 동시성을 위해 MVCC(Multi Version Concurrency Control)을 사용하지 않음
- 트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정 및 입력이 불가능하다.
Problem : Phantom Read 방지
MVCC란?
다중 사용자 데이터베이스 성능을 위한 기술로 데이터 조회 시 LOCK을 사용하지 않고 데이터의 버전을 관리해 데이터의 일관성 및 동시성을 높이는 기술
@Transaction(isolation=Isolation.DEFAULT)
public void test(String a) {...}
트랜잭션 동작 도중 다른 트랜잭션을 호출하는 상황에 선택할 수 있는 옵션이다.
해당 메소드는 트랜잭션이 필요하다. 트랜잭션을 새로 열거나 기존에 트랜잭션을 사용한다.
@Transaction(propagation = Propagation.REQUIRED)
public void doSomething() {...}
부모 트랜잭션이 존재하면 트랜잭션을 실행하고 없으면 필요없다.
@Transactional(propagation = Propagation.SUPPORT)
public void doSomething() {...}
부모 트랜잭션이 존재하면 부모 트랜잭션으로 동작하고 없으면 예외 발생
@Transactional(propagation = Propagation.MANDATORY)
public void doSomething() {...}
메소드 자기 자신만의 트랜잭션을 필요로 한다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething() {...}
트랜잭션 필요없고 이미 실행중인 트랜잭션이 있으면 중지시킨다.
@Transactional(propagation = Propagation.NOT_SUPPORT)
public void doSomething() {...}
이미 실행중인 트랜잭션이 존재하면 에러 발생
@Transactional(propagation = Propagation.NEVER)
public void doSomething() {...}
SAVEPOINT를 활용해서 부분 롤백이 가능하다.
@Transactional(propagation = Propagation.NESTED)
public void doSomething() {...}