[JDBC] 트랜잭션 - 스프링과 문제 해결

2024. 11. 8. 15:11·JDBC
목차
  1. 기술스택
  2. 1. 개요
  3. 문제점
  4. 문제 정리
  5. 2. 트랜잭션 추상화
  6. 트랜잭션 추상화 개요
  7. 스프링의 트랜잭션 추상화
  8. 3. 트랜잭션 동기화
  9. 4. 트랜잭션 문제 해결 - 트랜잭션 AOP 이해
  10. 스프링이 제공하는 트랜잭션 AOP
  11. 5. 트랜잭션 문제 해결 - 트랜잭션 AOP 정리

기술스택

  • Spring Boot 3.3.5
  • Java 17
  • H2 Database

1. 개요

문제점

전 포스팅에 만들었던 코드를 보자. 전체 내용은 여기 를 클릭하면 된다.

MemberServiceV2.java

@Slf4j
@RequiredArgsConstructor
public class MemberServiceV2 {
private final DataSource dataSource;
private final MemberRepositoryV2 memberRepository;
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(); // 실패시 롤백
e.printStackTrace();
throw new IllegalStateException(e);
}finally {
release(con);
}
}
private static void release(Connection con) {
if (con != null) {
try {
con.setAutoCommit(true); // 커넥션 풀 고려
con.close();
}catch (Exception e) {
log.info("error", e);
}
}
}
private static void validation(Member toMember) {
if (toMember.getMemberId().equals("ex")){
throw new IllegalStateException("이체중 예외 발생");
}
}
}

현재 서비스 계층인 MemberServiceV2.java 에서 DataSource, Connection, SQLException과 같은 JDBC 기술에 의존하고 있다. 만약 JDBC에서 JPA와 같은 다른 기술로 바꾸게 된다면 어떻게 될까? 바로 서비스 코드를 모두 함께 변경해야 한다. 

서비스 코드가 한개라면 상관이 없겠지만 수백 수천개가 된다면....

 

문제 정리

지금 이코드의 문제를 정리하자면 크게 세가지로 정리할 수 있다.

  1. 트랜잭션 문제
  2. 예외 누수 문제
  3. JDBC 반복 문제

1. 트랜잭션 문제

  • 서비스 계층 코드에 JDBC 구현 기술이 서비스 계층에 누수되는 문제
  • 트랜잭션 동기화 문제
  • 코드 반복이 많은 트랜잭션 적용 반복 문제

2. 예외 누수

  • JDBC 구현 기술 예외가 서비스 계층으로 전파되는 문제
  • SQLException은 JDBC 전용 기술이다. 향후 다른 데이터 접근 기술을 사용하면 그에 맞는 다른 예외로 변경해야 한다. 

3. JDBC 반복 문제

  • try, catch, finally와 같은 유사한 코드 반복이 많은 문제

스프링은 서비스 계층을 순수하게 유지하면서, 이러한 문제들을 해결할 수 있는 기술들을 제공한다. 

 

 

2. 트랜잭션 추상화

트랜잭션 추상화 개요

이 문제를 해결할려면 트랜잭션 기능을 추상화 하면된다. 단순하게 생각하면 다음과 같은 인터페이스를 만들어서 사용하면 된다.

 

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

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

 

그리고 다음과 같이 TxManager 인터페이스 기반으로 각각의 기술에 맞는 구현체를 만들면 된다.

추상화 관게

 

스프링의 트랜잭션 추상화

스프링은 이미 이런 고민을 다 해두었다. 우리는 굳이 인터페이스와 구현 클래스를 만들 필요 없이 스프링이 제공하는 트랜잭션 추상화 기술을 사용하면 된다. 심지어 데이터 접근 기술에 따른 구현체도 대부분 만들어두어서 가져다 사용하기만 하면 된다. 

PlatformTransactionManager 로직

스프링의 트랜잭션 추상화 핵심은 PlatformTransactionManager 이다.

 

PlatformTransactionManager.java

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

 

 

3. 트랜잭션 동기화

스프링이 제공하는 트랜잭션 매니저는 크게 2가지 역할을 한다.

  1. 트랜잭션 추상화
  2. 리소스 동기화

트랜잭션 매니저와 트랜잭션 동기화 매니저

스프링은 트랜잭션 동기화 매니저를 제공한다. 이것은 쓰레드 로컬(ThreadLocal)을 사용해서 커넥션을 동기화 해준다. 트랜잭션 매니저는 내부에서 이 트랜잭션 동기화 매니저를 사용한다. 

트랜잭션 동기화 매니저는 쓰레드 로컬을 사용하기 때문에 멀티쓰레드 상황에 안전하게 커넥션을 동기화할 수 있다. 따라서 커넥션이 필요하면 트랜잭션 동기화 매니저를 통해 커넥션을 획득하면 된다.

 

 

4. 트랜잭션 문제 해결 - 트랜잭션 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 Service {
public void logic() {
//트랜잭션 관련 코드 제거, 순수 비즈니스 로직만 남음
bizLogic(fromId, toId, money);
}
}

 

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

스프링이 제공하는 AOP 기능을 사용하면 프록시를 매우 편리하게 적용할 수 있다.  스프링 부트를 사용함녀 트랜잭션 AOP를 처리하기 위해 필요한 스프링 빈들도 자동으로 등록해준다. 개발자는 트랜잭션 처리가 필요한 곳에 @Transactional 애노테이션만 붙여주면 된다. 스프링의 트랜잭션 AOP는 이 애노테이션을 인식해서 트랜잭션 프록시를 적용해준다.

 

@Transactional를 사용한 MemberServiceV3_3.java

@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);
}
// 이하 생략...
}

 

이제 테스트를 진행 해보자.

@Slf4j
@SpringBootTest
class MemberServiceV3_3Test {
public static final String MEMBER_A = "memberA";
public static final String MEMBER_B = "memberB";
public static final String MEMBER_EX = "ex";
@Autowired
private MemberServiceV3_3 memberService;
@Autowired
private MemberRepositoryV3 memberRepository;
@TestConfiguration
static class TestConfig{
@Bean
DataSource dataSource(){
return new DriverManagerDataSource(URL, USERNAME, PASSWORD);
}
@Bean
PlatformTransactionManager transactionManager(){
return new DataSourceTransactionManager(dataSource());
}
@Bean
MemberRepositoryV3 memberRepositoryV3(){
return new MemberRepositoryV3(dataSource());
}
@Bean
MemberServiceV3_3 memberServiceV3_3(){
return new MemberServiceV3_3(memberRepositoryV3());
}
}
@Test
@DisplayName("이체중 예외 발생")
void accountTransferEx() throws SQLException {
// given
Member memberA = new Member(MEMBER_A, 10000);
Member memberEx = new Member(MEMBER_EX, 10000);
memberRepository.save(memberA);
memberRepository.save(memberEx);
// when
assertThatThrownBy(()-> memberService.accountTransfer(memberA.getMemberId(), memberEx.getMemberId(), 2000))
.isInstanceOf(IllegalStateException.class);
// then
Member findMemberA = memberRepository.findById(memberA.getMemberId());
Member findMemberB = memberRepository.findById(memberEx.getMemberId());
assertThat(findMemberA.getMoney()).isEqualTo(10000);
assertThat(findMemberB.getMoney()).isEqualTo(10000);
}
}

 

테스트가  성공하는 것을 볼 수 있다.

 

5. 트랜잭션 문제 해결 - 트랜잭션 AOP 정리

트랜잭션 AOP 적용 전체 흐름

  1. 프로세스 호출: 클라이언트, 테스트 케이스 또는 인터페이스 호출이 발생.
  2. 트랜잭션 매니저: 스프링 컨테이너에 등록된 트랜잭션 매니저가 동작.
  3. 트랜잭션 시작: transactionManager.getTransaction()을 통해 트랜잭션 매니저를 통해 트랜잭션이 시작.
  4. 데이터소스 연결 생성: 데이터소스를 통해 커넥션이 생성되고, 자동 커밋을 비활성화 (con.setAutoCommit(false)).
  5. 커넥션 보관: 커넥션이 보관됨.
  6. 트랜잭션 동기화 매니저: 트랜잭션 동기화를 위한 커넥션이 관리.
  7. 보관된 커넥션 획득: 동기화된 트랜잭션 커넥션을 통해 커넥션을 획득.
  8. 실제 서비스 호출: 서비스 로직이 호출되며, 트랜잭션 처리 로직을 통해 데이터 액세스 로직에 전달.
  9. 트랜잭션 처리 종료: 트랜잭션 처리 완료 후 AOP 프록시를 통해 트랜잭션이 종료.

 

 

'JDBC' 카테고리의 다른 글

[JDBC] 스프링과 문제 해결 - 예외 처리, 반복  (0) 2024.11.12
[JDBC] 자바 예외 이해  (1) 2024.11.09
[JDBC] 트랜잭션 이해  (0) 2024.11.08
[JDBC] 커넥션풀과 데이터소스 이해  (0) 2024.11.07
[JDBC] JDBC 이해  (0) 2024.11.07
  1. 기술스택
  2. 1. 개요
  3. 문제점
  4. 문제 정리
  5. 2. 트랜잭션 추상화
  6. 트랜잭션 추상화 개요
  7. 스프링의 트랜잭션 추상화
  8. 3. 트랜잭션 동기화
  9. 4. 트랜잭션 문제 해결 - 트랜잭션 AOP 이해
  10. 스프링이 제공하는 트랜잭션 AOP
  11. 5. 트랜잭션 문제 해결 - 트랜잭션 AOP 정리
'JDBC' 카테고리의 다른 글
  • [JDBC] 스프링과 문제 해결 - 예외 처리, 반복
  • [JDBC] 자바 예외 이해
  • [JDBC] 트랜잭션 이해
  • [JDBC] 커넥션풀과 데이터소스 이해
Economy98
Economy98
공부하고 기록하기
  • Economy98
    Economy_Dev
    Economy98
  • 전체
    오늘
    어제
    • 분류 전체보기 (74)
      • Spring Framework (11)
      • BOJ, Programmers (22)
      • Java (4)
      • JDBC (6)
      • JPA (9)
      • Spring Transaction (3)
      • Algorithm (1)
      • Web (5)
      • Projects (2)
        • 쇼핑몰 프로젝트 (0)
        • 열람실 & 도서관 프로젝트 (2)
      • Network (2)
      • 나의 공부방 (5)
      • 끄적끄적 (1)
      • Error Log (3)
      • CS (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • Github
  • 링크

    • Github
  • 공지사항

  • 인기 글

  • 태그

    다이나믹 프로그래밍
    브루트포스 알고리즘
    백준 자바 풀이
    백준
    트랜잭션
    Spring
    스프링
    자바 문제
    정렬
    java
    자바
    예외 처리
    jdbc
    그리디 알고리즘
    백준 풀이
    스프링부트
    propagation
    자바 문제 풀이
    JPA
    restful api
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
Economy98
[JDBC] 트랜잭션 - 스프링과 문제 해결

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.