본문 바로가기
Spring/토비의 스프링 3.1

토비의 스프링 chap 5 - 서비스 추상화

by 성건희 2022. 7. 1.
반응형

JdbcTemplate update 테스트

JdbcTemplate 으로 update() 메서드를 테스트 할 경우 수정하지 않아야 할 로우를 수정했는지에 대한 테스트도 하는것이 좋다. 해결 방법은 JdbcTemplate 의 update() 가 돌려주는 리턴 값이 수정된 로우의 개수이기 때문에 이것으로 판단할 수 있다. 다른 방법으로는 테스트를 보강해서 원하는 사용자 외에 정보는 변경되지 않음을 직접 확인하는 방법이 있다.

객체지향 적인 코드

Level 에 대한 부분을 Enum 으로 만들었는데, 정작 코드는 아래와 같이 사용한다.

public void upgradeLevel(User user) {
    if (user.getLevel() == Level.BASIC) {
        user.setLevel(Level.SILVER);
    }
    ...
}

get 으로 상태를 가져와서 직접 값을 비교하는 절차지향적인 방식으로 짜여져 있다.
사실 위와 같은 코드가 실제 회사에서 자주 보이는데, 확실히 해당 부분이 어떤 역할을 수행하는지 알아보려면 코드를 유심히 쳐다봐야한다.
따라서 이것을 객체지향 적인 코드로 개선하면 아래와 같다.

public void upgradeLevels() {
    List<User> users = userDao.getAll();

    for (User user : users) {
        if (canUpgradeLevel(user)) {
            upgradeLevel(user);
        }
    }
}

private boolean canUpgradeLevel(User user) {
    Level currentLevel = user.getLevel();
    switch (currentLevel) {
        case BASIC: return (user.getLogin() >= 50);
        case SILVER: return (user.getRecommend() >= 30);
        case GOLD: return false;
        default: throw new IllegalArgumentException("Unknown Level: " + currentLevel);
    }
}

private void upgradeLevel(User user) {
    user.upgradeLevel();
    userDao.update(user);
}
@Getter @Setter
@AllArgsConstructor
public class User {

    private String id;
    private String name;
    private String password;
    private Level level;
    private int login;
    private int recommend;

    public void upgradeLevel() {
        Level nextLevel = level.nextLevel();
        if (nextLevel == null) {
            throw new IllegalStateException(level + "은 업그레이드가 불가능합니다.");
        }
        level = nextLevel;
    }
}

레벨에 대한 처리를 Service 단에서 처리하는 게 아닌, User 도메인 스스로 처리 한다.
확실히 메서드 명만 보고 '아 이렇게 되겠구나~' 하는 것을 느낄 수 있을 것이다.

이러한 개발 방법이 처음에는 되게 어렵기 때문에, '객체지향의 사실과 오해', '오브젝트' 등의 개발도서를 읽으면서 토이 프로젝트로 꾸준히 의식적인 연습을 해야한다. 나또한 아직 많이 부족하다..ㅠ

트랜잭션

모든 사용자에 대한 레벨 업그레이드 작업은 전체가 다 성공하던지 아니면 전체가 다 실패해야 한다.
취소되는 작업을 트랜잭션 롤백이라고 하며, 반대로 모두 성공적으로 수행된 후 DB 에 반영하는 것을 트랜잭션 커밋이라고 한다.
setAutoCommit(false) 로 트랜잭션 시작을 선언하고 commit() 또는 rollback() 으로 트랜잭션을 종료하는 작업을 트랜잭션의 경계설정이라고 한다.
하나의 DB 커넥션 안에서 만들어지는 트랜잭션을 로컬 트랜잭션이라고 한다.

트랜잭션 동기화

트랜잭션 동기화란 UserService에서 트랜잭션을 시작하기 위해 만든 Connection 오브젝트를 특별한 저장소에 보관해두고, 이후에 호출되는 DAO 의 메서드에서 저장된 Connection 을 가져다가 사용하게 하는 것이다.
이렇게 트랜잭션 동기화 기법을 사용하면 파라미터를 통해 일일히 Connection 오브젝트를 전달할 필요가 없어진다.
스프링이 제공하는 트랜잭션 동기화 관리 클래스는 TransactionSynchronizationManager 다.
Connection 을 가져올 때는 DataSourceUtils.getConnection() 을 통해서 가져오게 되는데,
이 메서드는 Connection 오브젝트를 생성해줄 뿐만 아니라 트랜잭션 동기화에 사용하도록 저장소에 바인딩해주기 때문이다.

JdbcTemplate 과 트랜잭션 동기화

JdbcTemplate은 영리하게 동작하도록 설계되어 있다.
만약 미리 생성되서 트랜잭션 동기화 저장소에 등록된 DB 커넥션이나 트랜잭션이 없는 경우에는 JdbcTemplate 에 직접 DB 커넥션을 만들고 트랜잭션을 시작해서 JDBC 작업을 진행한다. 반면 트랜잭션 동기화를 시작해놓았다면 그때부터 실행되는 JdbcTemplate 의 메서드에서 직접 DB 커넥션을 만드는 대신 트랜잭션 동기화 저장소에 들어 있는 DB 커넥션을 가져와서 사용한다.

트랜잭션 서비스 추상화

하나 이상의 DB 를 하나의 트랜잭션으로 만드는건 JDBC Connection 을 이용한 로컬 트랜잭션으로는 불가능하다.
로컬 트랜잭션은 하나의 DB Connection 에 종속되기 때문이다.
따라서 별도의 트랜잭션 관리자를 통해 트랜잭션을 관리하는 글로벌 트랜잭션 방식을 사용해야 한다.
자바는 JDBC 외에 글로벌 트랜잭션을 지원하는 JTA (Java Transaction API) 를 제공하고 있다.
JTA 를 이용해 트랜잭션 매니저를 활용하면 여러 개의 DB나 메시징 서버에 대한 작업을 하나의 트랜잭션으로 통합하는 분산 트랜잭션 또는 글로벌 트랜잭션이 가능해진다.

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

스프링이 제공하는 트랜잭션 경계설정을 위한 추상 인터페이스는 PlatformTransactionManager 다.
JDBC 의 로컬 트랜잭션을 이용한다면 PlatformTransactionManager 를 구현한 DataSourceTransactionManager 를 사용하면 된다.

트랜잭션 기술 설정의 분리

JTA 를 이용하는 글로벌 트랜잭션으로 변경하려면, PlatformTransactionManager 의 구현 클래스를 JTATransactionManager 로 바꿔주기만 하면 된다.
스프링 빈의 설정만 바꿔주면 다른 어떤 코드도 손을 댈 필요가 없다.
사실 예제에서는 Service 단에서 PlatformTransactionManager 를 속성으로 가져오는데, 실제 현업에서는 @Transactional 어노테이션으로 사용하지 이렇게 쓰는건 본적이 없긴하다..

참고

  • 토비의 스프링 3.1 Vol. 1
반응형

댓글