본문 바로가기
공부/Spring

[Spring][인프런 스프링 DB] 트랜잭션(Transaction) 문제 해결

by 웅대 2023. 6. 25.
728x90
반응형

본 포스팅은 김영한 강사님의 인프런 강의 "스프링 DB 1편"을 정리한 포스팅입니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1/dashboard

 

스프링 DB 1편 - 데이터 접근 핵심 원리 - 인프런 | 강의

백엔드 개발에 필요한 DB 데이터 접근 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - 강의

www.inflearn.com

애플리케이션 구조는 보통 컨트롤러, 서비스, 레포지토리 계층으로 이루어진다.

 

이 중 서비스 계층의 비즈니스 로직은 특정 기술에 종속적이지 않게 개발해야 한다.

 

컨트롤러나 레포지토리의 경우 특정 기술에 종속적이어도 된다.

 

서비스 계층이 특정 기술에 종속적이지 않아야 유지보수도 쉽고 테스트도 쉽다.

 

최대한 순수한 자바 코드로 이루어지는 편이 좋다.

 

트랜잭션 추상화

 

<이전 포스팅 코드 예시>

public void accountTransfer(String formId, String toId, int money) throws SQLException, IllegalStateException {
    Connection conn = dataSource.getConnection();
    try {
        conn.setAutoCommit(false); //수동 커밋 설정. 트랜잭션 시작.
        //비즈니스 로직 작성
        conn.commit(); //데이터베이스 반영
    } catch (Exception e) {
        conn.rollback(); // 오류 나면 롤백
        throw e;
    } finally {
        if (conn != null) {
            conn.setAutoCommit(true);
            conn.close();

        }
    }
}

위 메소드는 서비스 계층에서 사용한 메소드이다.

 

트랜잭션을 시작하는 코드는 jdbc와 jpa가 다르다. 그런데 지금 서비스 계층에서 jdbc 기술에 의존하고 있다.

 

그래서 트랜잭션을 인터페이스로 추상화 한 다음 이 인터페이스를 의존하도록 변경해야한다.

 

트랜잭션 동기화

트랜잭션을 유지하기 위해 같은 커넥션을 사용해야한다. 이전 포스팅에서는 파라미터로 커넥션을 전달하는 방식을 사용했다.

 

그런데 스프링에서 제공해주는 트랜잭션 동기화 매니저를 사용하면 파라미터 방식을 사용하지 않아도 된다.

트랜잭션 동기화 매니저는 쓰레드 로컬을 사용해서 멀티쓰레드 상황에 커넥션을 안전하게 동기화 할 수 있다.

 

적용

트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용해서 커넥션을 획득하고 닫는다.

 

DataSourceUtils.getConnection(datasource)을 사용하면 트랜잭션 동기화 매니저가 관리하는 커넥션이 있으면 해당 커넥션을 반환하고 없다면 새로운 커넥션을 생성해서 반환한다.

 

DataSourceUtils.releaseConnection(connection, datasource)을 사용하면 트랜잭션을 위해 동기화된 커넥션을 닫지 않는다.

 

동기화 매니저가 관리하는 커넥션이 없으면 커넥션을 닫는다.

 

transactionManager.getTransaction()을 통해서 TransactionStatus를 얻을 수 있다.

 

이 TransactionStatus는 트랜잭션의 정보가 담겨있고 커밋(transactionManager.commit(status)), 롤백(transactionManager.rollback(status))할 때 필요하다.

 

transactionManager는 내부에서 데이터소스를 통해 커넥션을 생성한다.

 

동작 방식을 정리하면 다음과 같다.

  1. transaction manager가 트랜잭션을 시작하면 내부에서 데이터소스를 사용해서 커넥션을 생성한 후 자동 커밋을 사용하지 않게 설정한 후 트랜잭션 동기화 매니저에 커넥션을 넣어둔다.
  2. 트랜잭션 동기화 매니저는 쓰레드 로컬에 커넥션을 보관한다.
  3. 이렇게 보관된 커넥션은 DataSourceUtils.getConnection()을 통해서 꺼내올 수 있다.
  4. transaction manager가 트랜잭션 동기화 매니저에서 보관된 커넥션을 가져와서 commit하거나 rollback한다.
  5. 커넥션의 자동 커밋을 다시 설정해주고 종료한다. (커넥션 풀을 사용한다면 반환될 것이다.)

이제 TransactionTemplate을 사용하면 공통적인 코드를 제거할 수 있다. (try, catch, finally, commit, rollback등등..)

 

TransactionTemplate은 인자로 TransactionManager를 필요로 한다.

 

transactionTemplate.execute()는 응답 값이 있을 때 사용하고 transactionTemplate.executeWithoutResult()는 응답 값이 없을 때 사용한다.

 

이후 서비스 계층에 순수한 비즈니스 로직만 남기고 싶다면 AOP를 통해 프록시를 도입하면 된다.

이전
AOP 프록시 적용

이러한 AOP는 직접 구현해도되지만 @Transactioinal 어노테이션 하나로 매우 간편하게 구현할 수 있다.

 

최종 흐름

참고로 datasource와 transaction manager는 application.properties 설정을 통해 자동으로 빈에 등록할 수 있다.

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=

 

728x90
반응형

댓글