티스토리 뷰

0. 배경

자바 EE 어플리케이션 개발 시, 로컬 트랜잭션과 글로벌 트랜잭션의 필요성에 의해 각각의 스펙과 구현들이 등장

트랜젝션의 방식 내용
1. 로컬 트랜잭션 (Local Transaction) - 단일 데이터소스(단일 데이터베이스) + 단일 Connection 에 종속
    :: 하나의 DB 연결만을 다루며, 트랜잭션이 하나의 DB 내에서만 수행
- JDBC 기반에서 흔히 사용
2. 글로벌 트랜잭션 (Global Transaction)  - 다수 데이터소스(다수 데이터베이스) + 단일 Connection 종속성 제거
    :: 여러 DB에서의 트랜젝션
- 2PC (2-Phase Commit Protocol) 또는 XA 프로토콜로 커밋 / 롤백
- JTA와 JNDI 기술을 통해 트랜잭션과 데이터소스를 연결하고 관리

출처 : https://jiwondev.tistory.com/154

- 구현체마다 동일한 로직은 AbstarctPlatformTransactionManager 내 일부분이 구현 -> 재사용
1) 기본 구현체는 DataSourceTransactionManager
2) Spring Data JPA 사용 시,JpaTransactionManager로 설정
  • Spring Transaction :: 로컬 / 글로벌 트랜잭션의 단점을 보완한 Spring 표준 트랜잭션
    1. 트랜잭션 동기화
      :: 트랜잭션을 유지하려면 Connection 유지가 필요 -> 별도의 공간에 보관
      - 기존에는 다수 데이터소스에 대한 트랜잭션을 위해 Connection 객체를 파라미터로 계속 넘겨줌
      - 보완 내용 :: TransactionSynchronizationManager + DataSourceUtils 를 통해 Connection 보관 및 사용

    2. 트랜잭션 추상화
      :: 트랜잭션의 공통 부분을 추상화 -> 어떤 기술, 로컬 / 글로벌 트랜잭션 모두 사용 가능한 표준형상
      - 기존에는 트랜잭션 기술마다 각 정의된 트랜잭션 세부 구현체들을 가져다 구현이 필요했음
      - PlatformTransactionManager 인터페이스 구현체 통해 트랜잭션 시작과 끝 Commit, Rollback을 표기
      - JTA TransactionManager 와 구별 위해 PlatformTransactionManager라 명칭

상세한 내용들은 아래에 기술


1. 트랜잭션 동기화

:: TransactionSynchronizationManager내 Connection 보관

:: 이전 포스팅의 JDBC API에서는 트랜잭션 동기화를 위해 매번 Connection을 파라미터로 전달

:: Spring은 트랜잭션의 동기화를 위해 Connection을 꺼내 사용할 수 있는 중간 저장소를 제공

구분 내용
TransactionSynchronizationManager :: 스레드 기반으로 Connection 을 저장
- 내부적으로 ThreadLocal 을 사용하여, 스레드마다 독립적으로 Connection 객체 보관
-> 멀티 스레드 환경에서도 유리
DataSourceUtils :: 저장된 Connection 을 꺼내어 사용할 수 있도록 도움
onnection 가져올때 DataSource 가 필요하기 때문에 설정 필요
// application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/database-name
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

1.1 예시

항목 내용
- TransactionSynchronizationManager + DataSourceUtils :: ThreadLocal 기반 Connection 보관을 하여,
:: (1) 트랜잭션 생성 + (3) 트랜잭션 처리 ← 명시적
- DataSourceUtils :: 어디서든 Connection 꺼내쓸 수 있게 제공하여,
:: (2) 트랜잭션 참여 ← 명시적

 

===> 위를 통해 아래와 같이 단축

과정 
1) 트랜잭션 생성 Connection 생성 TransactionSynchronizationManager가 관리
2) 트랜잭션 참여 기존에 앞서 생성된 Connection 에 연결  DataSourceUtils (파라미터 미사용)
3) 트랜잭션 처리 성공 시 Commit 혹은 실패 시 Rollback  TransactionSynchronizationManager가 관리

 

EX)

더보기

1) 기존 코드 MessagejdbcApiDao.java

// ...
public List<Message> save(final Connection connection, Integer userId, String message) throws SQLException {
	if (true) {
		throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "트랜잭션 롤백 여부를 확인하기 위한 의도된 예외");
     }
// ...

 

- 변경 후의 코드

// ...
public List<Message> save(/* final Connection connection, */Integer userId, String message) throws SQLException {
	if (true) {
    	throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "트랜잭션 롤백 여부를 확인하기 위한 의도된 예외");
    }
   	Connection connection = DataSourceUtils.getConnection(dataSource);
// ...

 

2) 기존의 코드 UserService.java

public UserResponseDto save(String name, Integer age, String job, String specialty) {
    Connection connection = null;

    try {
        connection = dataSource.getConnection();    // Connection 생성
        connection.setAutoCommit(false);            // Connection Auto-Commit 옵션 끄기
        
        User user = userJdbcRepository.save(connection, name, age, job, specialty);
        List<Message> messages = messageJdbcRepository.save(connection, user.getId(), user.getName() + "님 가입을 환영합니다.");
        
        connection.commit();                        // (A) Commit
        
        UserResponseDto result = UserResponseDto.from(user);
        result.setMessages(messages);
        
        return result;
        
    } catch (SQLException e) {
        try {
            connection.rollback();                  // (B) Rollback
        } catch (final SQLException ignored) {
        }
        
        throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "자원 반납 시 문제가 있습니다.");
        
    } finally {
        try {
            connection.close();                     // (C) Close
        } catch (final SQLException ignored) {
        }
    }
}

 

- 이후 적용 코드

public UserResponseDto save(String name, Integer age, String job, String specialty) {
    // 트랜잭션 초기화
    TransactionSynchronizationManager.initSynchronization();

    // Connection 생성
    Connection connection = DataSourceUtils.getConnection(dataSource);

    try {
        connection.setAutoCommit(false);            // Connection Auto-Commit 옵션 끄기
        
        // 사용자 정보 저장
        User user = userJdbcRepository.save(name, age, job, specialty);
        
        // 메시지 저장
        List<Message> messages = messageJdbcRepository.save(user.getId(), user.getName() + "님 가입을 환영합니다.");
        
        connection.commit();                        // (A) Commit
        
        // 응답 DTO 생성
        UserResponseDto result = UserResponseDto.from(user);
        result.setMessages(messages);
        
        return result;
        
    } catch (SQLException e) {
        try {
            connection.rollback();                  // (B) Rollback
        } catch (final SQLException ignored) {
        }
        
        // 예외 처리
        throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "자원 반납 시 문제가 있습니다.");
        
    } finally {
        // Connection 반납
        DataSourceUtils.releaseConnection(connection, dataSource);
    }
}

1.2 트랜잭션 추상화

- Spring의 DB 접근 기술 :: 순수 JDBC, JPA, R2DBC, 등 - 서로 다른 트랜잭션 처리 방법임
1) 기술을 바꿀 경우, 트랜잭션을 사용하는 코드를 모두 수정 필요.. ㅠㅠ
2) 이에 Spring은 TransactionManager 라는 트랜잭션 공통 부분을 추상화 솔루션을 통해 일부분 공통화

 

 

- 트랜잭션의 공통 부분인 PlatformTransactionManager 인터페이스를 각 기술별 구현체로 사용

- Spring은 각 DB 기술에 대한  TransactionManager 구현체를 자동으로 Bean에 등록 

기술 내용
1) DataSourceTransactionManager (로컬) Spring 의 DataSource Bean 사용 및 MyBatis 의 SqlSessionTemplate 에도 적용
2) JpaTransactionManager (로컬)  -
3) HibernateTransactionManager (로컬)  -
4) JtaTransactionManager (글로벌)  2PC + XA 프로토콜이 적용된 JTA 사용하여 다수 데이터소스에 글로벌 트랜잭션을 적용
5) JmsTransactionManager (로컬) JMS(Java Message System) 메세지 큐 서버에 트랜잭션을 적용하고싶을때

1.2.1 예시

@Service
@RequiredArgsConstructor
public class SomeService {
//  환경에 맞는 트랜잭션 매니저 Bean 주입도 가능
//  private final PlatformTransactionManager transactionManager;
    private final DataSource dataSource;

    public UserResponseDto save(String name, Integer age, String job, String specialty) {
//      여기에서는 Bean 주입이 아닌 직접 정의하여 사용
        PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
        
        // 트랜잭션 세부 정의 : Propagation, Isolation, Timeout, ReadOnly
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        
        // 옵션 정의 예시        
        // TransactionDefinition definition = new DefaultTransactionDefinition() {{
        //    setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);  // Propagation :: 전파 범위, 새로운 트랜잭션 시작
        //    setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);    // Isolation Level :: 고립 레벨, 가장 낮은 고립 수준
        //    setTimeout(30);  // Timeout :: 롤백되는 한계시간 표기, 30초 후 타임아웃
        //    setReadOnly(true);  // Read-only :: 읽기 전용 트랜잭션
        // }};
		
        // 1. 트랜잭션 시작
        TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
        try {
            // 비즈니스 로직 실행
            ...
	          // 2.1. 트랜잭션 정상 종료
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
		        // 2.2. 트랜잭션 오류 종료
		        transactionManager.rollback(transactionStatus);
        }
    }
}

 

=> 이처럼 추상화 도입을 통해 부모에서 Connection에 대한 처리를 안해도 된다.. (Spring의 자동 주입)

=> 이해를 위해, React와 비교하자면 전역 상태 관리였던 Redux안에 저장된 값을 공유하듯이 여기서는 ThreadLocal에서       전역적으로 연결을 관리(= 하나의 연결에 대해 부모에서 Prop Drilling처럼 내려주기를 안해도 된다! 개꿀)


1.2.2 PlatformTransactionManager 사용 시 JdbcTemplate 의 의의

  • 1 쿼리 : 1 트랜잭션은 변화가 없지만, N 쿼리 : 1 트랜잭션의 경우,
    DataSourceUtils 없이도 트랜잭션의 추상화 / 동기화 / 고도화가 가능해졌다.
    1. 트랜잭션 추상화는 (1) PlatformTransactionManager을 통해 해결
    2. 트랜잭션 추상화 + 트랜잭션 동기화 (TransactionSyncrhonizationManager)
      • (2-1) 선언적(Declarative) 트랜잭션 관리 :: @Transactional (Spring AOP 사용)
      • (2-2) 프로그래밍형(Programmatic) 트랜잭션 관리 :: TransactionTemplate
  • 즉, JDBC Template에서 PlatformTransactionManager를 통해,
    1) 명시해줘야했던 Connection을 내부에서 사용함으로써, Connection의 내부화(트랜잭션 참여의 내재화 / 동기화)
    2) 모든 트랜잭션 로직의 구현체가 무엇이든 상관없이 일관된 처리가 가능해졌다.

Ex)  PlatformTransactionManager + JDBC Template을 통한 고수준의 동기화 

출처 : https://coding-business.tistory.com/82

더보기

- 추가적인 예시

출처 : https://obv-cloud.com/39
절차 내용
1) Transaction 시작 - 새 PlatformTransactionManager 생성
2) Datasource 에서 Connection 획득 - 구현체가 DataSourceTransactionManager이므로,
   PlatformTransactionManager.getTransaction(definition)
3) 획득한 Connection 을 TransactionSynchronizationManager 의 ThreadLocal 영역에 저장
4) Repository 에서 TransactionSynchronizationManager 을 통해 Connection 꺼내와서 처리 - JDBC API 사용 시 :: DataSourceUtils 에서 Connection
- JdbcTemplate 사용 시 :: 알아서 자동으로 Connection 
5) 로직 수행 완료
6) Transaction commit or rollback - PlatformTransactionManager.commit(status)
- PlatformTransactionManager.rollback(status)
7) TransactionSynchronizationManager 의 ThreadLocal 영역에서 Connection 제거
8) Datasource 에 Connection 반환 = DataSourceUtils.releaseConnection(connection, dataSource)

 


참고

ASAC 수업자료

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함