지금까지 유저 레벨 관리 예제를 들어서 트랜잭션 동기화 기법을 사용해 UserService 의 upgradeLevels 트랜잭션 처리를 구현하였습니다.

문제는 트랜잭션 동기화를 관리하는 TransactionSynchronizationManger 클래스와 동기화 저장소에 Connection을 바인딩해주는 DataSourceUtils 클래스를 사용함에 있어서 JDBC 에 종속적인 Connection을 이용한 코드가 UserService 로직에 들어가야 한다는 것입니다.

이전에 UserService는 인터페이스 UserDao 를 DI 받음으로써 Data Access 기술에 독립되고 있었는데, 다시 JDBC 에 종속되어버렸습니다.

단일 DB에서 JDBC를 이용한 트랜잭션이든 다중 DB에서 JTA(Java Transaction API)을 이용한 트랜잭션이든 Data Access 기술에 상관없이 트랜잭션 경계설정을 위한 패턴은 유사한 구조를 가지고 있습니다(트랜잭션 시작 ~ 트랜잭션 종료(commit/rollback))

그렇기에 스프링은 트랜잭션 기술의 공통점을 담은 트랜잭션 추상화 기술을 제공하고 있습니다.

트랜잭션 서비스 추상화


스프링이 제공하는 트랜잭션 경계 설정을 위한 추상 인터페이스는 PlatformTransactionManager 입니다.

UserService는 PlatformTransactionManager 인터페이스에 의존하고, 어떤 Data Access 기술을 사용하는지에 따라 적절하게 DI 받으면 됩니다.

기존의 단일 DB 에서 JDBC 기반 트랜잭션 동기화 기법을 이용하고 싶으면 PlatformTransactionManager 를 구현한 DataSourceTransactionManager 클래스를 이용하면 됩니다.

한 번 UserService를 다음과 같이 수정해보겠습니다.

public class UserService {
    ...

    private UserDao userDao;
    private PlatformTransactionManager transactionManager;

    public UserService(UserDao userDao, PlatformTransactionManager transactionManager) {
        this.userDao = userDao;
        this.transactionManager = transactionManager;
    }
    ...

    public void upgradeLevels() {
        //트랜잭션 시작
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            List<User> users = userDao.findAll();
            for (User user : users) {
                if (canUpgradeLevel(user)) {
                    upgradeLevel(user);
                }
            }
            transactionManager.commit(status); //트랜잭션 커밋
        } catch (RuntimeException e) {
            transactionManager.rollback(status); //트랜잭션 롤백
            throw e;
        }
    }

    protected void upgradeLevel(User user) {
        user.upgradeLevel();
        userDao.update(user);
    }

    private boolean canUpgradeLevel(User user) {
        ...
    }
}

기존에 DataSource 를 DI 받는 것을 PlatformTransactionManager 클래스를 DI 받는 것으로 변경하였고, 해당 transactionManager 제공하는 API를 이용하여 트랜잭션 처리를 하였습니다.

이전에 Connection을 생성하고 트랜잭션 동기화를 직접 관리하는 로직과 비교하면 엄청 깔끔해졌음을 알 수 있습니다.

또한, throw Exception 구문도 다시 사라져서 해당 메서드를 호출할 때 명시적으로 예외를 처리할 필요도 없어졌습니다.

소스를 살펴보면, transactionManger 의 getTransaction()을 호출하면 트랜잭션이 시작되는 것이며, connection을 얻거나 트랜잭션 동기화 저장소 관리 등의 작업은 PlatformTransactionManger 구현체가 알아서 수행해줍니다.

getTransaction 메서드를 호출할 때 파라미터로 넘기는 DefatultTranscationDefinition 오브젝트는 트랜잭션에 대한 속성을 담고 있습니다.

작업이 정상적으로 끝나면 commit 메서드를 호출하고 예외가 발생했다면 rollback 메서드를 호출하면 됩니다.