[Spring Boot] 10. 트랜잭션 전파 속성(Transaction Propagation)

하림·2024년 9월 2일

Spring

목록 보기
13/16
post-thumbnail

1. 트랜잭션 전파 속성

1.1 트랜잭션 전파 옵션




2. 프로젝트 생성 및 설정

다음과 같이 프로젝트를 생성합니다.

  1. 기존 프로젝트인 B08aMybatis를 복사하여 이름을 변경합니다.
  2. 프로젝트명 우클릭 > Properties > Web Project Settings 에서 Context root를 프로젝트명으로 변경합니다.
  3. settings.gradle 에서 rootProject.name 을 프로젝트명으로 변경합니다.
  4. Refresh Gradle Project 를 눌러 적용합니다.

이전 프로젝트(TransactionTemplate을 활용한 트랜잭션 관리)의 구성을 그대로 유지한채 파라미터 부분만 수정해봅니다.




3. 트랜잭션 전파 확인하기

3.1 소스코드 작성하기

서비스를 생성합니다. 다음과 같이 com.edu.springboot.jdbc.ITicketService.java 파일에 코드를 추가합니다.

package com.edu.springboot.jdbc;

import org.apache.ibatis.annotations.Mapper;

/* 구매한 티켓과 금액에 대한 insert 처리를 위한 추상메서드 정의 */
@Mapper
public interface ITicketService {
	// 매개변수는 커맨드 객체로 처리하기 위해 각 DTO 객체를 사용
	public int ticketInsert(TicketDTO ticketDTO);
	public int payInsert(PayDTO payDTO);
	
	public int memberRegist(TicketDTO ticketDTO);
}

매퍼를 생성합니다. 다음과 같이 src/main/resources/mybatis/mapper/TicketPayDAO.xml 파일에 코드를 추가합니다.

<insert id="memberRegist"
	parameterType="com.edu.springboot.jdbc.TicketDTO">
INSERT INTO member (id, pass, name) VALUES (#{userid}, '^^', '티켓구매')	
</insert>

클래스를 생성합니다. 다음과 같이 com.edu.springboot.jdbc.AddMember.java 파일을 작성합니다.

package com.edu.springboot.jdbc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

/* 스프링 컨테이너 시작시 자동으로 기본패키지를 스캔(Scan)한 후 빈을 생성한다. */
@Service
public class AddMember {
	
	// DB 작업을 위한 자동주입
	@Autowired
	ITicketService dao;
	// 트랜젝션 처리를 위한 자동주입
	@Autowired
	TransactionTemplate transactionTemplate;
	
	/* 전파속성
	 * REQUIRED : 기존 트랜젝션에 의존한다. 즉 포함된 메서드나 포함시킨 메서드 어느쪽이든 오류가 발생하면 모든 작업이 롤백된다. 즉 모든 메서드로 전파된다.
	 * REQUIRES_NEW : 각각의 트랜젝션을 처리한다. 즉 포함시킨 메서드에서 오류가 발생되더라도 포함된 메서드에서는 정상 처리된다. 포함된 메서드로 전파되지 않는다. */
	// @Transactional(propagation = Propagation.REQUIRED)
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void memberInsert(TicketDTO ticketDTO, String errFlag) {
		try {
			transactionTemplate.execute(new TransactionCallbackWithoutResult() {
				
				@Override
				protected void doInTransactionWithoutResult(TransactionStatus status) {
					// 체크박스 2를 선택하면 여기서 의도적 에러 발생됨
					if (errFlag != null && errFlag.equals("2")) {
						int money = Integer.parseInt("200원");
					}
					// 구매한 사람의 내역을 member 테이블에 추가
					int result3 = dao.memberRegist(ticketDTO);
					if (result3 == 1) {
						System.out.println("member 테이블 입력성공");
					}
				}
			});
		}
		catch (Exception e) {
			System.out.println("member 테이블 롤백");
		}
	}
}

컨트롤러를 생성합니다. 다음과 같이 com.edu.springboot.MainController.java 파일에 코드를 추가합니다.

package com.edu.springboot;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import com.edu.springboot.jdbc.AddMember;
import com.edu.springboot.jdbc.ITicketService;
import com.edu.springboot.jdbc.PayDTO;
import com.edu.springboot.jdbc.TicketDTO;

import jakarta.servlet.http.HttpServletRequest;
import oracle.jdbc.internal.OracleConnection.TransactionState;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class MainController {
	
	@Autowired
	ITicketService dao;
	
	@Autowired
	TransactionTemplate transactionTemplate;
	
	/* 추가작업 클래스 : 회원테이블에 구매한 사람의 이력을 입력
	 * 정의시 @Service 어노테이션을 부착한 상태이므로 자동 주입 가능함 */
	@Autowired
	AddMember addMember;
	
	@RequestMapping("/")
	public String home() {
		return "home";
	}
	
	@GetMapping("/buyTicket.do")
	public String buy1() {
		return "buy";
	}
	
	@PostMapping("/buyTicket.do")
	public String buy2(TicketDTO ticketDTO, PayDTO payDTO, HttpServletRequest req, Model model) {
		
		String viewPath = "success";
		
		
		try {
			transactionTemplate.execute(new TransactionCallbackWithoutResult() {
				
				@Override
				protected void doInTransactionWithoutResult(TransactionStatus status) {
					
					// 1. 회원정보입력 (추가작업, 클래스 외부에서 처리)
					String errFlag = req.getParameter("err_flag");
					addMember.memberInsert(ticketDTO, errFlag);
					
					// 2. 구매금액입력 (DB처리1)
					payDTO.setAmount(ticketDTO.getT_count() * 10000);
					int result1 = dao.payInsert(payDTO);
					if (result1 == 1) System.out.println("transaction_pay 입력성공");
					
					// 3. 비지니스로직 처리 (의도적 에러 발생 지점)
					if (errFlag != null) {
						int money = Integer.parseInt("100원");
					}
					
					// 4. 티켓 매수 입력 처리 (DB처리2)
					int result2 = dao.ticketInsert(ticketDTO);
					if (result2 == 1) System.out.println("transaction_ticket 입력성공");
					
					model.addAttribute("ticketDTO", ticketDTO);
					model.addAttribute("payDTO", payDTO);
				}
			});
		}
		catch (Exception e) {
			System.out.println("transaction 테이블 롤백");
			viewPath = "error";
		}
		return viewPath;
	}
}

뷰를 생성합니다. 다음과 같이 webapp/WEB-INF/views/buy.jsp 파일을 수정합니다.

<tr>
	<th>에러발생</th>
	<td>
		<input type="checkbox" name="err_flag" value="1" />
		티켓구매에서 예외발생
		<input type="checkbox" name="err_flag" value="2" />
		회원입력에서 예외발생
	</td>
</tr>

이제 트랜젝션 전파 속성을 확인할 수 있습니다.


2.2 REQUIRED 속성으로 실행하기

다음과 같이 실행됩니다.

아이디와 수량을 입력한 후 체크를 하지 않고 '전송하기'를 클릭하면 티켓 구매에 성공합니다.

콘솔에는 다음과 같이 출력됩니다. 모든 테이블이 입력됩니다.

member 테이블 입력성공
transaction_pay 입력성공
transaction_ticket 입력성공

이번에는 아이디와 수량을 입력한 후 '티켓구매에서 예외발생'을 체크하고 '전송하기'를 클릭합니다.

티켓구매 작업 수행 중 오류가 발생하여 티켓 구매에 실패합니다.

콘솔에는 다음과 같이 출력됩니다. 모든 테이블이 입력되지 않습니다.

member 테이블 입력성공
transaction_pay 입력성공
transaction 테이블 롤백

마지막으로 아이디와 수량을 입력한 후 '회원입력에서 예외발생'을 체크하고 '전송하기'를 클릭합니다.

회원입력 작업 수행 중 오류가 발생하여 티켓 구매에 실패합니다.

콘솔에는 다음과 같이 출력됩니다. 모든 테이블이 입력되지 않습니다.

member 테이블 롤백
transaction_pay 입력성공
transaction 테이블 롤백

2.3 REQUIRES_NEW 속성으로 실행하기

다음과 같이 실행됩니다.

아이디와 수량을 입력한 후 체크를 하지 않고 '전송하기'를 클릭하면 티켓 구매에 성공합니다.

콘솔에는 다음과 같이 출력됩니다. 모든 테이블이 입력됩니다.

member 테이블 입력성공
transaction_pay 입력성공
transaction_ticket 입력성공

이번에는 아이디와 수량을 입력한 후 '티켓구매에서 예외발생'을 체크하고 '전송하기'를 클릭합니다.

티켓구매 작업 수행 중 오류가 발생하여 티켓 구매에 실패합니다.

콘솔에는 다음과 같이 출력됩니다. 회원 테이블이 입력됩니다.

member 테이블 입력성공
transaction_pay 입력성공
transaction 테이블 롤백

마지막으로 아이디와 수량을 입력한 후 '회원입력에서 예외발생'을 체크하고 '전송하기'를 클릭합니다.

회원입력 작업 수행 중 오류가 발생하여 티켓 구매에 실패합니다.

콘솔에는 다음과 같이 출력됩니다. 모든 테이블이 입력되지 않습니다.

member 테이블 롤백
transaction 테이블 롤백

0개의 댓글