티스토리 뷰

데이터베이스/JDBC

[JDBC] 트랜잭션 매니저

거북이의 기술블로그 2024. 11. 5. 00:40

애플리케이션 구조

프레젠테이션 계층
> UI 와 관련된 처리 담당
> 웹 요청과 응답
> 사용자 요청 검증

서비스계층
> 비즈니스 로직 담당

데이터접근계층
> 실제 데이터베이스 접근 코드

 

서비스계층 트랜잭션 문제점 (JDBC)

public void func(String param1, String param2, int param3) throws SQLException{ // SQLEXCEPTION 예외
     Connection con = dataSource.getConnection();
     try{
         //1. JDBC 트랜잭션 시작
         con.setAutoCommit(false);
         bizLogic(con, param1, param2, param3);
         //2. JDBC 트랜잭션 끝
         con.commit();
     }catch (Exception e){
         con.rollback();
         throw new IllegalStateException(e);
     }finally{
         close(con);
     }
     //try ~ catch ~ finally 반복
}
  • 트랜잭션 문제
    • 서비스계층에서 Connection정보를 유지해야함 (Connection을 파라미터로 값을 넘기면서 동기화함)
  • 예외 누수 문제
    • SQLException 같은 경우, DB 접근시 JDBC에서 내보내던 예외로 주로 레포지토리에서 했었지만, 서비스계층의 트랜잭션으로 인해 예외처리가 넘어옴
  • JDBC 반복 문제
    • 서비스계층에도 JDBC가 반복적으로 등장함 (try ~ catch ~finally)
    • JPA , Mybatis, .. 등 기술이 바뀔때에도, 서비스계층의 수정이 필요하다

 

스프링 DB접근 인터페이스

 

  • 서비스계층에서는 PlatformTransactionManager를 통해 수정없이, DB 접근 방식을 변경해도된다
    • DataSourceTransactionManager : JDBC 접근
    • JPATransactionManager : JPA 접근
    • HibernateTransactionManager : Hibernate 접근
    • ...
  • PlatformTransactionManager 인터페이스
public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}
  • 메서드 설명
    • getTransaction() : 트랜잭션 시작
    • commit() : 커밋
    • rollback() : 롤백

 

트랜잭션 동기화 매니저

트랜잭션의 커넥션 동기화의 경우, 파라미터를 이용해 서비스계층에서부터 커넥션을 유지하며 트랜잭션을 진행하였다

[과정]
서비스계층(생성) -> 리포지토리 (DB) -> 서비스계층 (커넥션 해제)
  • 트랜잭션 동기화 매니저
    • 커넥션을 파라미터로 유지하는 대신, 커넥션을 유지할 수 있도록 도와주는 매니저
    • 쓰레드로컬(ThreadLocal)을 사용해서 커넥션을 동기화해준다
  • 트랜잭션 동기화 매니저 과정
    1. 서비스계층 트랜잭션 시작
    2. 트랜잭션 매니저 이용
    3. (동기화 매니저) 트랜잭션 매니저에서 준 커넥션 정보 저장
    4. 리포지토리, 동기화 매니저에서 커넥션을 가져다가 사용

 

 

트랜잭션 매니저 구현

  • Repository (Connection을 생성하지 않음)
    • DataSource 인터페이스로 커넥션 작업을 진행
    • DataSourceUtils.getConnection(dataSource)
      • DataSourceUtils는 트랜잭션 동기화 매니저가 관리하는 커넥션이 있으면 해당 커넥션을 반환 (없으면 새로 생성) 
    • close()
      • Connection을 직접 close해주면 안됨 ( 트랜잭션 동기화 매니저가 관리하고 있기 때문 )
public class MemberRepositoryV3 {

    private final DataSource dataSource;

    public MemberRepositoryV3(DataSource dataSource){
        this.dataSource = dataSource;
    }
    
    private void close(Connection con, PreparedStatement stmt, ResultSet rs){
        JdbcUtils.closeResultSet(rs);
        JdbcUtils.closeStatement(stmt);
        DataSourceUtils.releaseConnection(con, dataSource);
    }
    

    private Connection getConnection() throws SQLException{
        Connection con = DataSourceUtils.getConnection(dataSource);
        return con;
    }

    public Member save(Member member) throws SQLException {
        String sql = "insert into member(member_id, money) values(?,?)";

        Connection con = null;
        PreparedStatement pstmt = null;

        try{
            con = getConnection();
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, member.getMemberId());
            pstmt.setInt(2, member.getMoney());
            pstmt.executeUpdate();
            return member;
        }catch (SQLException e){
            throw e;
        }finally{
            close(con,pstmt, null);
        }
    }
//...
}
  • 서비스계층
    • transactionManager를 통해서 트랜잭션 실행
    • DefaultTransactionDefinition() : JDBC 트랜잭션 구현체 (다른 기술로 변경시 구현체만 변경)
@Slf4j
@RequiredArgsConstructor
public class MemberServiceV3 {
    private final PlatformTransactionManager transactionManager;
    private final MemberRepositoryV3 memberRepository;

    public void func(String fromId, String toId, int money) throws SQLException{

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

        try{
            bizLogic(fromId, toId, money);
            transactionManager.commit(status);
        }catch (Exception e){
            transactionManager.rollback(status);
            throw new IllegalStateException(e);
        }
    }

    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);

        if (toMember.getMemberId().equals("ex")){
            throw new IllegalStateException("이체중 예외");
        }
        
        memberRepository.update(toId, toMember.getMoney() +money);
    }
}

 

정리

  • 커넥션의 생성과 삭제 과정
    • 서비스 계층 : 커넥션이 생성되고 삭제됨 (DataSourceUtils)
    • 리포지토리 계층 : 트랜잭션 동기화 매니저를 통해서 커넥션을 동기화할 수 있음
      • (DataSourceUtils.releaseConnection() -> 바로 커넥션을 해제 하지 않기 위해서 )
  • DataSource / DataSourceUtils / TransactionManager
    • DataSource : 여러 DB접근들의 메서드들의 인터페이스
    • DataSourceUtils : 해당 getConnection을 통해 트랜잭션 동기화매니저가 커넥션을 관리
    • TransactionManager : 실제 DB접근 기술들에 따른 트랜잭션 인터페이스

 

 

'데이터베이스 > JDBC' 카테고리의 다른 글

@Transactional  (0) 2024.11.06
[JDBC] 트랜잭션 템플릿  (1) 2024.11.06
[JDBC] 트랜잭션  (0) 2024.11.05
[JDBC] 커넥션 풀  (7) 2024.11.04
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함