checked Exception rollback이 필요할 경우

이태규·2023년 10월 16일
0

spring

목록 보기
61/64

상황 : 한 로직에 DB insert를 실행한 후 HttpClient를 이용해 restAPI를 실행시킨 후 return 값으로 추가 로직을 처리해야 하는 상황 중 HttpClient method 실행 중 잘못된 api를 실행시키면 ClientProtocolException이 발생한다. Exception이 발생하면 트랜잭션 중 insert 상태를 rollback해야 하는데 ClientProtocolException은 checked Exception이기 때문에 rollback이 자동으로 일어나지 않는다.

요약: checked Exception이 발생했을 때 rollback 하는 방법

첫번째로 시도한 것은 @Transaction이다.
Transaction annotation은 checked Exception에서 자동으로 rollback해주지 않는다.

두번째 시도 @Transaction(rollbackFor)를 한다.

@Transactional(rollbackFor = { ClientProtocolException.class, ProtocolException.class })

실패....

세번째 시도 강제로 rollback을 시켜주기

	@Autowired
	private PlatformTransactionManager transactionManager;
    
    
	 @PostMapping("/test")
	 @ResponseBody
	 @Transactional public void test(@RequestBody ReqMap
	 reqMap, @AuthenticationPrincipal AdminUser user) {

	 DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
	 TransactionStatus status = transactionManager.getTransaction(definition);


	 try { 
     	throw new ClientProtocolException(); 
     } catch (ClientProtocolException e) {
	 	transactionManager.rollback(status); // 롤백
     }
}
	 

성공 그러나 return 값으로 ResMap.valueOf(ResultCode.SUCCESS);
코드 값을 넘겨야 하는데,
return 실행 후 proxy에서 추가 작업을 통해 resultMap이 덮어씌어짐.

대안으로 작성한 게 ClientProtocolException 발생 시 RuntimeException을 강제로 실행시켰음.

 try{}
 catch (ClientProtocolException e) {
			throw new RuntimeException();
		}

	/**
	 * 프로젝트 실행
	 */
	@PostMapping("/run")
	@ResponseBody
	public ResMap runProject(@RequestBody ReqMap reqMap, @AuthenticationPrincipal AdminUser user) {
		DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
		TransactionStatus status = transactionManager.getTransaction(definition);
		try {
			projectService.runProject(reqMap, user);
			return ResMap.valueOf(ResultCode.SUCCESS);
		}catch(RuntimeException e) {
			return postRollbackActions();
		} catch (Exception e) {
			return ResMap.valueOf(ResultCode.ERR_ETC);
		}
	}

GPT한테 물어보니 좋은 방법은 아니라고 함.

해결 방법이 아니었음. 결국 안되고 있었음.

의심을 한 건 트랜잭션 단위를 내가 잘못알고 있나였음.
client에서 server로 응답값을 보낼때 controller에서 응답값을 받고 return 해줄때 까지를 transction이라고 생각했음.
그리고 transactional의 단위를 확인하니

트랜잭션은 메소드 단위었음.

그리고 transactional은 service 계층에서 부여함.

	/**
	 * 프로젝트 실행
	 *
	 * @throws IOException
	 * @throws ClientProtocolException
	 */
	@Transactional(rollbackFor = {ClientProtocolException.class})
	public void runProject(ReqMap reqMap, AdminUser user) throws Exception {

		List<KngMap> keyList = reqMap.getContentList(); // 프로젝트일련번호 리스트


		for (KngMap key : keyList) {
			if (key.size() < 1)	throw new Exception("등록된 API가 없습니다.");
			List<CpaiApiMng> apiList = apiManageDAO.getApiList(key); // API관리 목록 조회

			CapiHistoryDetail projHist = insertRunHistory(key, user); // 프로젝트 실행이력 INSERT

			executeApi(apiList, projHist, user); // api 실행

		}
	}

결국 service계층에서
@Transactional(rollbackFor = {ClientProtocolException.class})를 사용해서 해결

컨트롤러에서 @transactional을 선언할 경우
spring AOP는 다이나믹 Proxy기법을 사용한다.
controller는 다이나믹 proxy기법을 사용하는 interface를 가지고 있지 않기 때문에 @transaction은 controller에서 작동하지 않는다.

application context 파일에서 다음과 같은 설정을 하면 사용 가능하다.

<annotation-driven>proxy-target-class="true"/></annotation-driven>  

일반적으로 controller까지 transcation을 가지고 있으면 비효율적이므로 서비스 계층에서 transactional을 걸어준다.

https://dimdim.tistory.com/entry/Spring-MVC-Controller-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%97%90-Transactional-%EC%82%AC%EC%9A%A9%EC%97%90-%EB%8C%80%ED%95%9C-%EC%82%BD%EC%A7%88

profile
한 걸음씩 나아가자

0개의 댓글