트랜잭션 추상화

wangjh789·2022년 8월 14일
0

[Spring] 스프링-DB-1

목록 보기
9/15

프레젠테이션 계층

  • UI 관련된 처리 담당
  • 웹 요청과 응답
  • 사용자의 요청 검증

서비스 계층

  • 비즈니스 로직

데이터 접근 계층

  • 실제 DB에 접근하는 코드

서비스 계층

서비스 계층은 특정 기술에 종속적이지 않은 순수한 자바 코드로 작성해 최대한 변경없이 유지되어야 한다.

    public void accountTransfer(String fromId,String toId,int money) throws SQLException {
        Member fromMember = memberRepositoryV1.findById(fromId);
        Member toMember = memberRepositoryV1.findById(toId);

        memberRepositoryV1.update(fromId, fromMember.getMoney() - money);
        validation(toMember);

        memberRepositoryV1.update(toId, toMember.getMoney() + money);
    }

위의 트랜잭션이 없는 서비스 로직은 대부분 순수한 자바 코드로 짜여져 있지만 SQLException이 JDBC 기술에 종속적인 에러이다. 만약 JPA 기술로 변경한다면 이 코드를 수정해야 할 것이다.

    public void accountTransfer(String fromId,String toId,int money) throws SQLException {
        Connection con = dataSource.getConnection();
        try {
            con.setAutoCommit(false);

            Member fromMember = memberRepository.findById(con, fromId);
            Member toMember = memberRepository.findById(con, toId);

            memberRepository.update(con, fromId, fromMember.getMoney() - money);
            validation(toMember);

            memberRepository.update(con, toId, toMember.getMoney() + money);

            con.commit();
        } catch (Exception e) {
            con.rollback();
            throw new IllegalStateException(e);
        }finally {
            if (con != null) {
                try {
                    con.setAutoCommit(true);
                } catch (Exception e) {
                    log.info("error", e);
                }finally {
                    JdbcUtils.closeConnection(con);
                }
            }
        }
    }

위 코드는 트랜잭션을 적용한 서비스 로직이다. 전의 코드보다 DataSource, Connection과 같이 JDBC 종속적인 기술을 더 많이 사용하고 있다.
또한 핵심 로직보다 트랜잭션을 사용하는 코드가 더 많고 비즈니스 로직과 JDBC 기술이 섞여 있어 유지보수하기 힘들다.

  • 트랜잭션 문제
  • 예외 누수 문제
  • JDBC 반복문제

트랜잭션 추상화

지금처럼 서비스와 레파지토리가 모두 JDBC 기술에 의존하고 있다면 단일 책임 원칙을 위반하는 것이다.
(변경 포인트는 하나여야 한다.)
스프링은 이런 문제를 해결하기 위해 트랜잭션 추상화를 위한 PlaformTransactionManager 트랜잭션 매니저 인터페이스와 여러 구현체들을 제공한다.

  • 트랜잭션 추상화
  • 리소스 동기화
    트랜잭션을 보장하기 위해 connection을 파라미터로 넘겼지만 그 결과로 코드가 더러워지고, 트랜잭션을 사용하는 메서드를 따로 만들어야 했다.
    스프링은 '트랜잭션 동기화 메니저'를 제공한다. 쓰레드 로컬을 사용해 커넥션을 동기화해준다. 트랜잭션 매니저는 내부에서 이 트랜잭션 동기화 매니저를 사용한다.

동작방식
1. 트랜잭션을 시작하려면 트랜잭션 매니저는 데이터소스를 통해 커넥션을 만들고 트랜잭션을 지삭한다.
2. 트랜잭션 매니저는 트랜잭션이 시작된 커넥션을 동기화 매니저에 보관한다.
3. 리포지토리는 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용한다.
4. 트랜잭션이 종료되면 트랜잭션이 매니저는 트랜잭션 동기화 매니저을 통해 트랜잭션을 종료하고 커넥션도 닫는다.

  • DataSourceUtils.getConnection()
    트랜잭션 동기화 매니저가 관리하는 커넥션이 있으면 해당 커넥션을 반환하고 그렇기 않을 경우 새로운 커넥션을 생성해 반환한다.
  • DataSourceUtils.releaseConnection()
    트랜젝션을 사용하기 위해 동기화된 커넥션을 닫지 않고 그대로 유지해주고 그렇기 않을 경우 커넥션을 닫는다.

Repository가 트랜잭션 유지할 지를 판단하는 방법은 트랜잭션이 트랜잭션 동기화 매니저에 의해 관리되고 있는지로 결정한다.

JDBC 종속성이 사라진 서비스 계층

public void accountTransfer(String fromId,String toId,int money) {
        TransactionStatus status = transactionManager
        								.getTransaction(new DefaultTransactionDefinition());
        try {

            Member fromMember = memberRepository.findById(fromId);
            Member toMember = memberRepository.findById(toId);

            memberRepository.update(fromId, fromMember.getMoney() - money);
            validation(toMember);

            memberRepository.update(toId, toMember.getMoney() + money);

            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw new IllegalStateException(e);
        }
    }

PlatformTransactionManager의 commit(), rollback() 이 수행되면 트랜잭션이 release 된다.

PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
JDBC의 DataSource와 동작하는 트랜잭션 매니저 구현체는 DataSourceTransactionManager 이다.

PlatformTransactionManager

  • getTransaction()
  1. 트랜잭션 매니저는 내부의 데이터 소스를 사용해 커넥션을 가져온다.
  2. 커넥션을 수동커밋 모드로 변경해서 실제 데이터베이스 트랜젝션을 시작한다.
  3. 커넥션을 트랜잭션 동기화 매니저에 보관한다.
  4. 트랜잭션 동기화 매니저는 쓰레드 로컬에 커넥션을 보관한다. (멀티 쓰레드 안전)
  • 로직 실행
  1. 서비스 계층이 비즈니스 로직을 수행하면서 레포지토리 메서드를 수행한다.
  2. 리포지토리는 DataSourceUtils.getConnection() 을 사용해 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용한다.(같은 세션, 트랜잭션 유지)
  3. 획득한 커넥션으로 SQL을 DB에 전달해 실행한다.
  • commit(), rollback()
  1. 트랜잭션 동기화 매니저를 통해 동기화된 커넥션을 획득한다.
  2. 획득한 커넥션을 통해 DB에 트랜잭션을 커밋하거나 롤백한다.
  3. 트랜잭션 동기화 매니저를 정리한다. (쓰레드 로컬은 사용 후 정리해야 함)
  4. 커넥션을 자동 커밋 모드로 돌려 놓고 커넥션 풀에 반환한다.

PlatformTransactionManager에 의해 서비스 계층은 특정관련 기술에 의존하지 않게 되었다.

profile
기록

0개의 댓글