본문 바로가기
Spring Study/DataBase

[DataBase] 트랜잭션 (Transaction)2

by 정재인 2023. 9. 19.

애플리케이션 구조

애플리케이션은 3가지 계층으로 나눌 수 있다. Controller, Service, Repository

프레젠테이션 계층 (Controller)

- UI와 관련된 처리 담당

- 웹 요청과 응답

- 사용자 요청을 검증

- 사용 기술: Servlet, HTTP 같은 웹 기술, 스프링 MVC

 

서비스 계층 (Service)

- 비즈니스 로직 담당

- 사용 기술: 특정 기술에 의존하지 않고, 순수 자바 코드로 작성

 

데이터 접근 계층(Repository)

- 실제 데이터베이스에 접근하는 코드

- 사용 기술: JDBC, JPA, File, Redis, Mongo ...

 

이 중 가장 중요한 곳은 서비스 계층이다.

 

서비스 계층은 시간이 흘러 UI(웹)와 관련된 부분이 변하고, 데이터 저장 기술이 다른 기술로 변해도, 비즈니스 로직은 최대한 변경없이 유지되어야 한다.

 

이를 위해, 서비스 계층을 특정 기술에 종속되지 않게 개발해야 한다.

 


트랜잭션 추상화

구현 기술에 따른 트랜잭션 사용법

트랜잭션은 원자적 단위의 비즈니스 로직을 처리하기 위해 사용된다.

구현 기술마다 트랜잭션을 사용하는 방법이 다르다.

- JDBC: con.setAutoCommit(false)

- JPA: transaction.begin()

 

JDBC 트랜잭션 코드 예시

public void accountTransfer(String fromId, String toId, int money) throws SQLException {
      Connection con = dataSource.getConnection();
      try {
          con.setAutoCommit(false); //트랜잭션 시작 
          //비즈니스 로직
          bizLogic(con, fromId, toId, money); 
          con.commit(); //성공시 커밋
       }catch (Exception e) { 
          con.rollback(); //실패시 롤백
          throw new IllegalStateException(e);
      } finally { release(con);
   } 
}

 

JPA 트랜잭션 코드 예시

public static void main(String[] args) {

    //엔티티 매니저 팩토리 생성
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
    EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성 EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
        
        try {
            tx.begin(); //트랜잭션 시작 
            logic(em); //비즈니스 로직 
            tx.commit();//트랜잭션 커밋
        } catch (Exception e) { 
            tx.rollback(); //트랜잭션 롤백
        } finally {
            em.close(); //엔티티 매니저 종료
        }
        emf.close(); //엔티티 매니저 팩토리 종료
}

 

JDBC 기술을 사용하다 JPA 기술로 변경하게 되면 서비스 계층의 코드도 JPA 기술을 사용하도록 함께 수정해야 한다.

 

이를 해결하기 위해서는 트랜잭션 기능을 추상화하면 된다.

다음과 같은 인터페이스를 만들어서 사용하면 된다.

 

트랜잭션 추상화 인터페이스

public interface TxManager{
    begin();
    commit();
    rollback();
}

트랜잭션을 시작하고, 비즈니스 로직의 수행이 끝나면 commit하거나 rollback한다.

 

다음과 같이 TxManager 인터페이스를 기반으로 각 기술에 맞는 구현체를 만든다.

- JdbcTxManager: JDBC 트랜잭션 기능을 제공하는 구현체

- JpaTxManager: JPA 트랜잭션 기능을 제공하는 구현체

 

이제 서비스는 특정 트랜잭션 기술에 직접 의존하는 것이 아니라, TxManager라는 추상화된 인터페이스에 의존하게 된다.

원하는 구현체를 DI를 통해 주입하면 된다. 

이를 통해 클라이언트인 서비스는 인터페이스에 의존하고 DI를 사용한 덕분에 OCP 원칙도 지키게 된다.

 

스프링이 제공하는 트랜잭션 추상화 인터페이스 

 

PlatformTransactionManager 인터페이스

package org.springframework.transaction;
    public interface PlatformTransactionManager extends TransactionManager {
      TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
              throws TransactionException;
      void commit(TransactionStatus status) throws TransactionException;
      void rollback(TransactionStatus status) throws TransactionException;
    }

 


트랜잭션 문제 해결 - 트랜잭션 AOP 이해

프록시를 통한 문제 해결

프록시 도입 전

프록시를 도입하기 전에는 기존처럼 서비스의 로직에서 트랜잭션을 직접 시작한다.

 

서비스 계층의 트랜잭션 사용 코드 예시

//트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
        //비즈니스 로직
        bizLogic(fromId, toId, money); 
        transactionManager.commit(status); //성공시 커밋
    }catch(Exception e) { 
        transactionManager.rollback(status); //실패시 롤백
        throw new IllegalStateException(e);
}

 

프록시 도입 후

프록시를 사용하면 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 분리할 수 있다.

 

트랜잭션 프록시 코드 예시

public class TransactionProxy {
    private MemberService target;

    public void logic() { 
        //트랜잭션 시작
        TransactionStatus status = transactionManager.getTransaction(..);
        try {
            //실제 대상 호출 
            target.logic();
            transactionManager.commit(status); //성공시 커밋 
        } catch (Exception e) {
            transactionManager.rollback(status); //실패시 롤백
            throw new IllegalStateException(e);
          }
    } 
}

 

트랜잭션 프록시 적용 후 서비스 코드 예시

public class Service{

    public void logic(){
        //트랜잭션 관련 코드 제거, 순수 비즈니스 로직만 남음
        bizLogic(fromId, toId, money);
    }
}

프록시 도입 전

- 서비스에 비즈니스 로직과 트랜잭션 처리 로직이 섞여있다.

프록시 도입 후

- 트랜잭션 프록시가 트랜잭션 처리 로직을 모두 가져간다.

- 트랜잭션을 시작한 후에 실제 서비스를 대신 호출한다.

- 트랜잭션 프록시 덕분에 서비스 계층에는 순수한 비즈니스 로직만 남길 수 있다.

 

스프링이 제공하는 트랜잭션 AOP

@Transactional

org.springframework.transaction.annotation.Transactional
package hello.jdbc.service;

import hello.jdbc.domain.Member;
import hello.jdbc.repository.MemberRepositoryV3;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

import java.sql.SQLException;
/**
* 트랜잭션 - @Transactional AOP
*/
@Slf4j
@RequiredArgsConstructor
public class MemberServiceV3_3 {
    private final MemberRepositoryV3 memberRepository;
    @Transactional
    public void accountTransfer(String fromId, String toId, int money) throws SQLException {
        bizLogic(fromId, toId, money);
    }
    private void bizLogic(String fromId, String toId, int money) throws SQLException {
        Member fromMember = memberRepository.findById(fromId);
        Member toMember = memberRepository.findById(toId);
        memberRepository.update(fromId, fromMember.getMoney() - money);
        validation(toMember);
        memberRepository.update(toId, toMember.getMoney() + money);
}
    private void validation(Member toMember) {
        if (toMember.getMemberId().equals("ex")) {
            throw new IllegalStateException("이체중 예외 발생"); }
    } 
}

 

선언적 트랜잭션 관리 (Declarative Transaction Management)

- @Transactional 애노테이션 하나만 선언해서 편리하게 트랜잭션을 적용하는 것

- 이름 그대로 해당 로직에 트랜잭션을 적용하겠다 라고 어딘가 선언하기만 하면 트랜잭션이 적용되는 방식

 

프로그래밍 방식의 트랜잭션 관리 (programmatic transaction management)

- 트랜잭션 매니저 또는 트랜잭션 템플릿 등을 사용해서 트랜잭션과 관련 코드를 직접 작성하는 것

 

'Spring Study > DataBase' 카테고리의 다른 글

[DataBase] 예외 처리  (0) 2023.09.21
[DataBase] 자바 예외 (Exception)  (0) 2023.09.20
[DataBase] 트랜잭션 (Transaction)  (5) 2023.09.18
[DataBase] Connection Pool / DataSource  (2) 2023.09.14
[DataBase] JDBC란?  (0) 2023.09.13

댓글