Java

Spring Transaction 관리

Daniel0617 2020. 2. 11. 21:00

글을 작성하는 목적

  • Transaction 이해
  • Transaction 코드 사용 예시
  • 스프링 프레임워크에서 구현한 Transaction 추상화

Transaction이란?

위키피디아에서는 트랜잭션을 '데이터베이스 관리 시스템 또는 유사한 시스템에서 상호작용의 단위'라고 설명한다. 이해되지 않아 다시 인터넷을 검색해보면 '데이터베이스의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한꺼번에 모두 수행되어야 할 일련의 연산들을 의미한다'라고 설명된다. 즉, 데이터베이스에 기능을 수행하기 위한 상호 작용하는 하나의 단위라고 볼 수 있으며, 안전하게 수행되는 것을 보장하기 위해 네가지의 성질을 갖고 있다.

원자성(Atomicity), 일관성(Consistency), 고립성(Isolation), 지속성(Durability) 성질을 갖고 있으며, 원자성은 트랜잭션과 관련 작업들이 부분적으로 실행되다가 중단되지 않는 것을 보장하는 것, 일관성은 트랜잭션이 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태로 유지하는 것, 고립성은 트랜잭션 수행 시 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장하는 것, 마지막으로 지속성은 성공적으로 수행된 트랜잭션은 영원히 반영되어야 함을 말한다.

Transaction을 사용하는 경우는?

그럼 프로그래밍에서 Transaction을 사용하는 적절한 경우는 언제일까?

나의 경우 결제 시스템에서 사용했다. 결제 시스템에서 트랜잭션을 사용한 이유는 여러 테이블에서 데이터를 입출력하는 과정중 어느 한 가지 과정에서라도 에러 또는 예외 사항이 발생하면 모든 데이터를 롤백해줘야 하기 때문이다. 결제는 완료되었으나 결제한 카드 정보를 볼 수 없는 경우와 같은 시스템 오류를 범하지 않기 위해서다. 결제 기능을 수행하기 위해 여러 수행 과정을 쪼갤 수 없는 하나의 단위로 바라보고, 트랜잭션을 사용한 것이다.

이외에도 트랜재션이 유지되어야 하는 경우는 많다. 예를 들면, 영화 티켓을 예매하는 경우 영화석 자리는 맡아놨으나 결제를 하지 않는 경우는 없다. ATM 기기에서 출금하는 경우 계좌에 출금액 만큼 금액이 변경되었으나 돈이 출금되지 않는 경우는 없을 것이다. 즉, 하나의 작업을 진행하던 중 어느것 하나라도 에러가 발생하면 모든 실행이 롤백되거나 완전히 실행될 수 있도록 만들어 주는 곳에서 트랜잭션을 사용한다고 볼 수 있다.

Transaction 사용

JDBC 트랜잭션 코드

아래 코드는 스프링의 Service 클래스에서 트랜잭션을 사용한 JDBC 코드이다.

public void payment() throws Exception {
    Connection c = dataSource.getConnection();  // DB 커넥션 생성
    c.setAutoCommit(false);  // 트랜잭션 시작

    try {
        paymentDao.account(account);          // 결제금액 저장
        paymentDao.paymentType(paymentType);  // 결제정보 저장(ex. 카드, 계좌이체 정보 등)

        c.commit();   // 트랜잭션 커밋
    }
    catch(Exception e) {
        c.rollback(); // 트랜잭션 롤백
    }
    c.close();  // DB 커넥션 종료
}

결제 기능 안에서 '결제금액 저장'과 '결제정보 저장'을 하나의 단위로 보고 트랜잭션으로 묶어줌으로써 어느 한부분에서라도 에러가 발생한다면 롤백 할 수 있도록 설정했다. setAutoCommit 메소드는 Connection이 하나의 SQL문을 실행할 때마다 자동적으로 커밋처리 해주는 것을 방지해줌으로써 에러 발생 시 모든 SQL문을 롤백 할 수 있도록 처리했다.

 

JDBC Transaction 코드의 한계점은

위의 내용처럼 '결제' 기능에서 트랜잭션을 적용한 것을 알 수 있으나 코드에서 치명적인 단점을 찾아 볼 수 있다. 데이터 엑세스 기술로부터 독립적이지 않다는 것이다. JDBC Transaction은 로컬 트랜잭션으로써 두 개 이상의 DB로의 작업을 하나의 트랜잭션으로 만드는 것이 불가능하다. 여러 개의 DB에 데이터를 넣는 작업을 해야하는 경우 JTA를 활용해야 하는데 이런 경우 JDBC 트랜잭션 설정 코드를 모두 변경해야 함으로 코드 수정 시 에러 발생 확률이 높아진다. 또한 Service 클래스에서 자신의 로직이 바뀌지 않았음에도 기술환경에 따라 코드가 바뀌는 코드가 돼버리고 있다. (이외에도 여러 문제점이 존재한다. 세부 내용을 확인하기 원한다면 '토비 스프링 5장. 서비스 추상화'를 봐볼 것!!!)

스프링의 트랜잭션 서비스 추상화

위의 문제점을 스프링에서는 어떻게 해결하는가?

스프링 프레임워크에서는 PlatformTransactionManager 추상화를 통해 개발자가 어떤 환경 속에서도 연속적인 프로그래밍 모델이 가능하도록 설계되었다.

(출처 : https://yangbongsoo.gitbooks.io/study/content/c11c\_be44\_c2a4\_cd94\_c0c1\_d654.html)

PlatformTransactionManager 인터페이스를 스프링 설정 파일을 통해 빈으로 등록하고 DI를 받아 사용한다면 JDBC 트랜잭션 코드 내용을 아래와 같이 수정 할 수 있다.

public class PaymentService{
...
    private PlatformTransactionManager transactionManager;

    public void setTransactionManager(PlatformTransactionManager transactionManager){
        this.transactionManager = transactionManager;
    }

    public void transactionCode() throws Exception {
        TransactionStatus status =
                this.transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            paymentDao.account(account);          // 결제금액 저장
            paymentDao.paymentType(paymentType);  // 결제정보 저장(ex. 카드, 계좌이체 정보 등)

            this.transactionManager.commit(status);
        } catch(RuntimeException e) {
            this.transactionManager.rollback(status);
            throw e;
        }
    }
}

설정 파일은 아래와 같다.

<bean id="paymentService" class="spring.test.service.PaymentService">
    <property name="paymentDao" ref="paymentDao" />
    <property name="transactionManager" ref="transactionManager" />
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionMager">
    <property name="dataSource" ref="dataSource" />
</bean>

PlatformTransactionManager에서 설정된 API를 사용함으로써 Service 클래스는 이전보다 독립적이고 확장될 수 있는 클래스가 되었다. 데이터 액세스 기술이 변경될 경우 설정 파일을 변경해줌으로써 일괄적인 적용이 가능하고, Service 클래스에서는 비지니스 로직 변경될 경우에만 수정될 수 있도록 설계된 것이다.

참고