[Spring] 스프링 트랜잭션 동기화

DYKO·2022년 11월 11일
1

Spring Framework

목록 보기
7/7

💡 트랜잭션이란

트랜잭션(Transaction): 더 이상 나눌 수 없는 업무 처리의 최소 단위

트랜잭션은 데이터베이스에 여러 개의 클라이언트가 동시에 액세스하거나 갱신을 처리하는 과정에서 중단 등으로 인한 데이터 부정합을 방지하기 위해 사용하는 것이다. 트랜잭션 시작 후 정상 처리 된 경우 commit()을 통해 데이터베이스에 변경 사항을 영구적으로 적용하고, 오류가 발생한 경우 rollback()을 수행하여 트랜잭션 내에서 작업했던 모든 내용을 취소하고 원래 상태로 롤백한다. 트랜잭션은 아래와 같은 성질을 갖는다.

  • 원자성 : 트랜잭션 연산이 모두 반영되거나 모두 반영되지 않아야 함
  • 일관성 : 트랜잭션이 완료되면 항상 일관성있는 상태를 유지해야 함
  • 독립성 : 이미 수행 중인 트랜잭션이 완료되기 전에 다른 트랜잭션의 수행 결과를 참조할 수 없음
  • 영속성 : 완료된 트랜잭션은 영구적으로 반영되어야 함

💡 JDBC 트랜잭션을 이용한 코드의 문제점

DB는 완벽한 트랜잭션을 지원하고 있지만, 서비스 중 여러 개의 SQL의 작업을 하나의 트랜잭션으로 취급해야 하는 경우가 있다. 예를 들면 계좌이체 처럼 출금 계좌의 잔고 수정과 입금 계좌의 잔고 수정의 2개 작업이 모두 같이 롤백/커밋 되어야 하는 경우가 그렇다.

일반적인 JDBC를 이용해 사용자 관리를 하는 예제 코드를 보자.

Connection c = dataSource.getConnection();
c.setAutoCommit(false); //JDBC의 트랜잭션은 작업마다 오토커밋하도록 되어 있어 해당 기능 false 처리

try{
	PreparedStatement updateSt = c.prepareStatement("update users ...");
	updateSt.executeUpdate();

	PreparedStatement deleteSt = c.prepareStatement("delete users ...");
	deleteSt.executeUpdate();

	c.commit();
} catch(Exception ex){
	c.rollback();
}
 finally {
	 c.close();
}

일반적인 JDBC의 트랜잭션을 이용하게 되면 예제와 같이 Connection을 선언하여 트랜잭션을 시작하고 복수의 작업을 완료 후에 commit()/rollback()을 호출하여 여러 개의 작업을 하나의 트랜잭션으로 묶을 수 있다. 하지만 템플릿을 통해 데이터 엑세스 코드를 DAO로 만들어 분리한 경우에는 메서드를 호출할 때마다 DAO 안에서 새로운 트랜잭션을 사용하여 DB 작업을 처리하기 때문에 위 방법을 활용할 수 없다. 다수의 작업을 한 트랜잭션으로 묶기 위해 DAO의 메서드를 호출할 때 마다 Connection 객체를 매개변수로 받아 처리하게 되면 문제는 해결되지만, 대신 다른 문제가 발생하게 된다. Connection 생성 및 try/catch/finally 블록이 비즈니스 로직 내에 존재해야 하여 DB 작업만 분리 해놓은 템플릿을 활용 할 수 없게 된다. 또한 DB에 의존적이 되어 데이터 접근 로직을 수정 시에 비즈니스 로직도 영향을 받게 된다. 이를 해결하기 위해 스프링은 트랜잭션 동기화 방식을 제공하고 있다.


💡 트랜잭션 동기화 (Transaction Synchronization)

트랜잭션 동기화란 비즈니스 로직을 담은 객체에서 만든 Connection 객체를 특별한 저장소에 보관해두고, 이후에 호출되는 DAO의 메서드에서는 저장된 Connection을 가져다가 사용하게 하는 방식이다. 트랜잭션 동기화 방식을 사용하면 매개변수를 통해 Connection 객체를 넘길 필요가 없어진다.

트랜잭션 동기화를 사용한 경우의 작업 흐름은 다음과 같다.

https://velog.velcdn.com/images/dyko/post/77f157da-e6bd-4b73-94be-e737cf9b8dc0/image.png

(1) UserService는 Connection 생성
(2) Connection을 트랜잭션 동기화 저장소에 저장 후, setAutoCommit(false)를 통해 트랜잭션을 시작시킴
(3) 첫 번째로 UserDao의 update() 메소드 호출
(4) JdbcTemplate 메서드에서 트랜잭션 동기화 저장소에 현재 시작된 트랜잭션을 가진 Connection이 존재하는지 확인 후 이를 가져옴
(5) Connection을 이용해 PreparedStatement를 만들어 update SQL 실행
(6) 두 번째 update() 호출
(7) 트랜잭션 동기화 저장소에서 Connection 가져옴
(8) update SQL 실행
(9), (10), (11) 앞의 과정과 동일하게 세 번째 update SQL 실행됨
(12) 트랜잭션 내의 모든 작업이 정상적으로 끝났을 때, UserService는 Connection의 commit()을 호출하여 트랜잭션을 완료 시킴
(13) 트랜잭션 저장소에서 완료한 Connection 객체 제거

위와 같은 방식으로 트랜잭션 동기화를 사용하기 위해 스프링에서 제공하는 클래스가 TransactionSynchronizationManager이다. 해당 클래스를 통해 트랜잭션 동기화 처리를 초기화 및 종료할 수 있다. 해당 클래스를 사용하여 트랜잭션 동기화 작업을 하는 예제 코드이다.

// Connection 생성 시, 사용할 DataSource를 DI 받도록 함
private DataSource dataSource;

public void setDataSource(DataSource dataSource) {
	this.dataSource = dataSource;
}

public void upgradeLevels() throws Exception {
	// 트랜잭션 동기화 관리자를 통해 동기화 작업 초기화
	TransactionSynchronizationManager.initSynchronization();
    Connection c = DataSourceUtils.getConnection(dataSource);
    c.setAutoCommit(false);

    try{
    	List<User> users = userDao.getAll();
        for (User user : users) {
        	if (canUpgradeLevel(user)) {
            	upgradeLevel(user);
            }
        }
        c.commit();
    } catch(Excepton e) {
    	c.rollback();
        throw e;
    } finally {
    	// 스프링 유틸리티 메서드를 이용하여 안전하게 DB Connection close
    	DataSourceUtils.releaseConnection(c, dataSource);
        // 동기화 작업 종료 및 정리
        TransactionSynchronizationManager.unbindResource(this.dataSource);
        TransactionSynchronizationManager.clearSynchronization();
    }
}

사용자 레벨을 업그레이드하는 다중 작업을 하나의 트랜잭션으로 처리하기 위해, JDBC의 트랜잭션 경계설정 메서드를 사용하는 코드에 트랜잭션 동기화 작업을 붙여주었다. 또한 Connection을 생성하기 위해 스프링에서 제공하는 DataSourceUtils.getConnection() 메서드를 사용했는데, 그 이유는 해당 메서드를 사용하면 Connection을 생성하는 것 뿐만 아니라 트랜잭션 동기화 저장소에 바인딩까지 해주기 때문이다. 이와 같이 트랜잭션 동기화를 사용함으로써 Connection 객체를 매개변수로 받지 않아도 깔끔하게 트랜잭션 처리를 할 수 있게 됐다.

한계와 해결방안

그러나 위와 같이 로컬 트랜잭션을 직접 처리하게 되면 코드가 기술환경에 종속되게 되는 문제가 또한 발생한다. 이를 해결하기 위해 스프링은 트랜잭션 서비스 추상화 기술을 지원하고 있다. 해당 내용은 [Spring] 서비스 추상화(PSA) 포스팅에서 볼 수 있다.



참고문헌

토비의 스프링 3.1 Vol. 1 스프링의 이해와 원리, 이일민, 에이콘출판
트랜잭션 - 해시넷

profile
엔지니어가 되는 그 날 까지!

0개의 댓글