본문 바로가기
백엔드/Spring Framework

트랜잭션 처리

by maverick11471 2024. 7. 12.

1. 정의

[예시: 은행 계좌 이체]
은행 시스템에서 A 계좌에서 B 계좌로 금액을 이체하는 과정을 예로 들어보겠습니다. 
이 과정은 여러 단계로 이루어지며, 각 단계가 성공적으로 완료되어야만 전체 트랜잭션이 성공합니다.

송금 계좌(A)의 잔액 감소
수취 계좌(B)의 잔액 증가
이 두 단계 중 하나라도 실패하면 전체 트랜잭션은 롤백되어야 합니다. 
예를 들어, A 계좌에서 금액을 차감했지만 B 계좌에 금액을 추가하는 데 실패하면, 
A 계좌의 차감된 금액도 원래 상태로 복원되어야 합니다.
[정의]
DB 처리하는 작업 단위
DDL(Insert, Delete, Update) 실행 시 항상 commit, rollback 시행되야 트랜잭션이 종료됨
스프링에서 AOP 설정을 이용해서 트랜잭션을 처리

 

2. 스프링에서의 트랜잭션

트랜잭션 설정 시 aop에서 aspect 대신 advisor 사용
  (이유 : 개발자가 commit과 rollback 사용 시점을 지정할 수 없기 때문)

advisor는 쿼리가 성공하면 commit, 실패하면 rollback을 시행함.

 

 

3. root-context.xml 수정

요약
이 설정을 통해, 데이터베이스 작업 중 오류가 발생했을 때 데이터의 일관성이 유지되며, 
특정 메소드에 트랜잭션을 적용하거나 제외할 수 있습니다. 
트랜잭션 관리를 통해 애플리케이션이 안정적이고 신뢰성 있게 동작할 수 있도록 보장합니다.

// beans 추가
<beans xmlns:tx="http://www.springframework.org/schema/tx"> 

// xsi:schemaLocation 추가
<xsi:schemaLocation=http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--Transaction 설정-->
    <!--Tansaction을 관리하는 객체를 bean으로 등록-->
    //DataSourceTransactionManager는 Spring에서 제공하는 트랜잭션 매니저로, 
    // 데이터 소스와 연결된 트랜잭션을 관리합니다.
    // dataSource를 참조하여 데이터베이스 연결을 설정합니다.
    // 이를 통해 트랜잭션 매니저가 데이터베이스와의 트랜잭션을 관리할 수 있게 됩니다.
    <bean id="txManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--DML(insert, update, delete)가 종료되면 commit이나 rollback이 자동 실행되도록 설정
        TransactionManager만 등록한다고 Transaction이 자동 관리되진 않고
        AOP 설정을 통해서 Transaction이 공통 기능으로 동작되도록 설정해야 된다.
    -->
    <!-- Transcation은 advice를 tx:advice 엘리먼트로 등록한다-->
    // 트랜잭션 어드바이스(tx:advice)는 AOP를 이용하여 트랜잭션을 적용할 메소드들을 정의합니다.
    // tx:attributes 내에서 트랜잭션이 적용될 메소드들을 지정할 수 있습니다.
    // get*으로 시작하는 메소드는 읽기 전용(read-only="true")으로 설정하여, 
    // 트랜잭션이 필요 없음을 명시합니다. 읽기 전용 트랜잭션은 데이터베이스의 변경을 허용하지 않습니다.
    // 나머지 모든 메소드(*)는 기본 트랜잭션 동작을 하도록 설정하여, 
    // 데이터베이스의 변경이 발생할 수 있는 메소드에 트랜잭션을 적용합니다.
    
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!--어떤 이름의 메소드에서 Transaction이 동작할 지 설정-->
        <tx:attributes>
            <!--get으로 시작하는 메소드에서는 Transaction 동작하지 않음-->
            <tx:method name="get*" read-only="true"/>
            <!--get으로 시작하는 메소드를 제외한 모든 메소드에서는 Transaction 동작-->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

 

4. FreeBoardDao 클래스 수정

    // 게시글 등록 쿼리
    // ID 추가
    private final String POST = "INSERT INTO FREEBOARD(ID, TITLE, CONTENT, WRITER_ID) VALUES(?, ?, ?, ?)";
    
public void post(BoardDto boardDto) {
        System.out.println("FreeBoardDao의 post 메소드 실행");

        jdbcTemplate.update(POST, boardDto.getId(), boardDto.getTitle(), boardDto.getContent(), boardDto.getWRITER_ID());

        System.out.println("FreeBoardDao의 post 메소드 실행 종료");
}

 

5. FreeBoardServiceImpl 클래스 수정

// freeBoardDao.post(boardDto); 2번 작성


@Override
    public void post(BoardDto boardDto) {     
        freeBoardDao.post(boardDto);
        freeBoardDao.post(boardDto);
    }

 

6. FreeBoardServiceRun 클래스 수정

// primary key 가 중복되도록
// boardDto.setId(50); 추가

public class FreeBoardServiceRun {
    public static void main(String[] args) {
        AbstractApplicationContext factory =
                new GenericXmlApplicationContext("root-context.xml");

        BoardService boardService = factory.getBean("freeBoardServiceImpl", BoardService.class);

        BoardDto boardDto = new BoardDto();
        boardDto.setId(50);

 

7. 결과

 - Duplicate entry(50) 중복된다고 나옴( FreeBoardServiceImpl 클래스에서 freeBoardDao.post(boardDto) 를 2번썼기 때문에..)

 - 쿼리를 보면 rollback 되면서 첫 번째 50도 삭제