출처 - https://github.com/jmxx219/CS-Study (opens in a new tab)
@Transactional
트랜잭션(Transacntion)이란?
- 개념
- DBMS(데이터베이스 관리 시스템) 또는 유사한 시스템에서 발생하는 연산들의 상호작용 단위
- 유사한 시스템이란 트랜잭션의 성공과 실패가 분명하고 상호독립적이며, 일관되고 믿을 수 있는 시스템을 의미함
- 데이터베이스의 상태를 변경시키기 위해 수행하는 작업 단위 또는 한꺼번에 모두 수행되어야 할 일련의 연산을 의미
- DBMS(데이터베이스 관리 시스템) 또는 유사한 시스템에서 발생하는 연산들의 상호작용 단위
- 필요성
- 데이터베이스의 데이터를 수정하는 도중에 예외가 발생할 경우, DB의 데이터들은 수정되기 전의 상태로 돌아가야 하고 다시 수정 작업이 진행되어야 함
- 이 때문에 데이터의 일관성을 유지하면서 안정적으로 데이터를 복구하기 위해서 트랜잭션을 사용함
- 특징
- ACID 원칙을 적용하여 데이터 일관성과 무결성을 보장함
- 트랜잭션 내 연산은 모두 독릭적으로 이루어지며, 그 과정 중간에 다른 연산은 할 수 없고 하나의 연산이라도 오류가 발생하면 해당 트랜잭션은 취소되고 모두 원래대로 되돌아가야 함
- 트랜잭션 연산(작업)
- 커밋: 하나의 트랜잭션이 성공적으로 끝났으며, 데이터베이스가 일관성이 있는 상태로 유지될 때 트랜잭션이 마무리되었다는 것을 트랜잭션 관리자에게 알리기 위한 연산
- 롤백: 트랜잭션 처리가 비정상적으로 종료된 경우, 트랜잭션을 다시 시작하거나 트랜잭션의 부분 연산 결과를 취소시킴
스프링에서 트랜잭션 처리
0. 순수 JDBCTemplete 사용
public class MemberService {
public void updateAge(Long memberId) {
Connection connection = dataSource.getConnection(); // (1)
try(connection){
connection.setAutoCommit(false); // (2)
// 비즈니스 로직 수행
Member member = memberRepository.findById(connection, memberId);
memberRepository.update(connection, memberId, member.getAge() + 1);
connection.commit(); // (3)
}catch(SQLException e){
connection.rollback(); // (4)
}finally{
connection.close(); // (5)
}
}
}
- 개념
- Java에서 데이터베이스 트랜잭션을 시작하는 유일한 방법으로, Connection 객체의 메서드를 이용하여 트랜잭션을 생성할 수 있음
- Spring의
@Transactional
도 JDBC의 기본 방식으로 동작함
- 동작 방식
- 데이터베이스를 쓰기 위해 연결 진행함(
Connection
객체 생성) setAutoCommit(false)
로 트랜잭션을 직접 관리할 수 있음- 비즈니스 로직 수행 뒤, commit 실행
- 예외가 발생했을 때 rollback 실행
- 데이터베이스를 쓰기 위해 연결 진행함(
- 문제점
- 트랜잭션 동기화 문제
- 개발자가 직접 여러 개의 작업을 하나의 트랜잭션으로 관리하려면
connection
객체를 공유하는 등 많은 작업들을 하게 됨- ex) 같은 트랜잭션을 유지하기 위해서
connection
을DAO
호출할 때마다 파라미터로 넘겨주어야 함
- ex) 같은 트랜잭션을 유지하기 위해서
- 해결:
Spring 트랜잭션 동기화
- 개발자가 직접 여러 개의 작업을 하나의 트랜잭션으로 관리하려면
- JDBC 구현 기술이 서비스 계층에 누수되는 문제
- 트랜잭션을 적용하기 위해 서비스 계층이 JDBC 기술에 의존되어있음(데이터 접근 기술에 의존적인 코드)
- 향후 JDBC에서 JPA와 같은 기술로 바뀌면 JDBC 의존성 코드 때문에 서비스 코드도 모두 변경해야하는 문제 발생
- 해결:
Spring 트랜잭션 추상화
- 순수 비즈니스 로직과는 다른 관심사의 일(DB 접근 관련)을 수행하고 있음
- 해결:
Spring 선언전 트랜잭션
- 해결:
- 트랜잭션 적용 코드 반복 문제(try, catch, finally ...)
- 트랜잭션 동기화 문제
스프링은 트랜잭션과 관련한 핵심 기술 3가지를 제공하고 있음
1. 트랜잭션 동기화
public void method() throws Exception {
// (1) 동기화 작업 초기화
TransactionSynchronizationManager.initSynchronization();
// (2) DB 커넥션 생성 및 트랜잭션 시작
Connection connection = DataSourceUtils.getConnection(dataSource);
connection.setAuthCommit(false);
try{
// 비즈니스 로직 수행
connection.commit(); // (3)
}catch(Exception e){
connection.rollback(); // (4)
throw e;
}finally{
DataSourceUtils.releaseConnection(connection, dataSource); // 커넥션 자원 해제
// (5) 동기화 작업 종료 및 정리
TransactionSynchronizationManager.unbindResource(this.dataSource);
TransactionSynchronizationManager.clearSynchronization();
}
}
- 개념
- 트랜잭션을 시작하기 위한
Connection
객체를 특별한 저장소에 보관해두고 필요할 때 꺼내쓸 수 있도록 하는 기술
- 트랜잭션을 시작하기 위한
TransactionSynchronizationManager
- 트랜잭션 동기화 저장소로, 작업 스레드마다 독립적으로
Connection
객체를 저장하고 관리함 - 작업 쓰레드마다
Connection
객체를 독립적으로 관리하기 때문에, 멀티쓰레드 환경에서도 충돌이 발생하지 않음 DataSourceUtils
을 사용해Connection
객체를 가져와 사용함
- 트랜잭션 동기화 저장소로, 작업 스레드마다 독립적으로
- 문제점: 여전히 data 접근 기술에 의존적임
2. 트랜잭션 추상화
public class MemberService {
private PlatformTransactionManager transactionManager;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void method() throws Exception {
// (1) 트랜잭션 시작
TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
try{
// 비즈니스 로직 수행
transactionManager.commit(status); // (2)
}catch(Exception e){
transactionManager.rollback(status); // (3)
throw e;
}
}
}
- 개념
- 스프링은 트랜잭션 기술의 공통점을 담은 트랜잭션 추상화 기술을 제공하고 있음
- 이를 이용하여 애플리케이션에 각 기술(JDBC, JPA, Hibernate 등)에 종속적인 코드를 이용하지 않고도 일관되게 트랜잭션을 처리할 수 있도록 함
- 기술 별로 가져오는 트랜잭션 형태만 다르고 기능은 같이 때문에 추상화가 가능함
- 스프링 트랜잭션 추상화의 핵심:
PlatformTransactionManager
인터페이스
PlatformTransactionManager
- 사용할 DB(JDBC)의
DataSource
를 생성자에 넣어 트랜잭션 추상 오브젝트를 생성하여 사용함 - 이때 환경에 맞는
TransactionManager
클래스를 DI 받아 사용함- JDBC:
transactionManager = new DataSourceTransactionManager(dataSource)
- JPA:
transactionManager = new JpaTransactionManager()
- JDBC:
getTransaction()
으로 트랜잭션을 시작함
- 사용할 DB(JDBC)의
- 문제점: 여전히 순수 비즈니스 로직과는 다른 관심사의 일(DB 접근)을 수행함
3. AOP를 이용한 트랜잭션 분리
@Transactional
public class MemberService {
public void updateAge(Long memberId) throws Exception {
// 비즈니스 로직 수행
Member member = memberRepository.findById(memberId);
memberRepository.update(memberId, member.getAge() + 1);
}
}
- 소스 코드에 직접 트랜잭션 관련 로직을 넣지 않고, 비즈니스 로직에서 완전히 분리하는 방식
- 스프링은 트랜잭션 코드와 같은 부가 기능 코드를 비즈니스 로직 클래스 밖으로 빼내서 별도의 모듈로 만드는 AOP를 고안함
- 이를 적용한 트랜잭션 어노테이션이
@Transactional
임
- 이를 적용한 트랜잭션 어노테이션이
- 이 어노테이션을 적용하면 핵심 비즈니스 로직만 남게 됨
@Transactional 어노테이션
- 개념
- 스프링에서 많이 사용되는 선언적 트랜잭션 방식으로, 적용된 범위가 트랜잭션이 되도록 보장해줌
- 선언적 트랜잭션이란 설정 파일 또는 어노테이션 방식으로 간편하게 트랜잭션에 관한 행위를 정의하는 것
getConnection()
,setAutoCommit(false)
, commit(),rollback()
등의 JDBC에서 필요한 코드를 삽입해줌
- 스프링에서 많이 사용되는 선언적 트랜잭션 방식으로, 적용된 범위가 트랜잭션이 되도록 보장해줌
- 사용
- Spring에서 ServiceLayer에 해당 어노테이션을 추가하여 트랜잭션 처리를 진행함
- 서비스 클래스 혹은 메서드 단위에 어노테이션을 추가하여 사용함
@Transactional
어노테이션을 사용하라면 설정에@EnableTransactionManagement
를 추가해야 하는데, 스프링 부트에서는 자동으로 설정되어 있음
- 옵션
isolation
: 트랜잭션에서 일관성 없는 데이터 허용 수준을 설정(격리수준)propagation
: 트랜잭션 동작 도중 다른 트랜잭션을 호출할 때, 어떻게 할 것인지 설정하는 옵션noRollbackFor
: 특정 예외 발생 시 rollback을 수행하지 않는 옵션rollbackFor
: 특정 예외 발생 시 rollback을 수행하는 옵션timeout
: 지정한 시간 내에 메소드 수행이 완료되지 않으면 rollback을 하는 옵션readOnly
: 트랜잭션을 읽기 전용으로 설정
isolation
@Trasanctional(isolation = Isolation.READ_UNCOMMITTED)
public static void main() {}
각 벤더별 Default 격리 수준
- ORACLE : READ COMMITED
- MySQL : REPEATABLE READ
- SQL Server : READ COMMITED
- PostgreSQL : READ COMMITED
➕ 읽기에 @Transactional(readonly=true)
를 붙이는 이유
- JPA의 경우, 해당 옵션을
true
로 설정하면 트랜잭션이 커밋되어도 영속성 컨텍스트 (opens in a new tab)를 플러시 하지 않음. - 플러시할 때 수행되는 엔티티는 변경 감지를 위한 스냅샵 비교 로직이 수행되지 않으므로 성능을 약간 향상 시킬 수 있음
동작 원리
- 트랜잭션은 스프링이 지원하는 어노테이션 기반 AOP를 통해 구현되어 있음
import org.springframework.transaction.annotation.Transactional
- 클래스나 메소드에 @Transactional이 선언되면 해당 클래스에 트랜잭션이 적용된 프록시 객체가 생성됨
- 프록시 객체는 @Transactional이 포함된 메서드가 호출될 경우, 트랜잭션을 시작하고 Commit 또는 Rollback을 수행함
CheckedException
or 예외가 없을 때Commit
수행UncheckedException
이 발생하면Rollback
수행
테스트 환경에서 @Transactional 동작
- 반복 가능한 테스트 지원
- 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 트랜잭션을 강제로 롤백함
- 해당 어노테이션이 테스트 케이스에서 사용될 때만 롤백됨