개발/JPA

[JPA] UnexpectedRollbackException 오류 발생

jjeong2v 2024. 5. 20. 15:11
org.springframework.transaction.UnexpectedRollbackException: 
Transaction silently rolled back because it has been marked as rollback-only

 

- 문제의 코드

logRepository.save(log);

try {
    if (!appVersionService.verify(request)) {
    	return getResult("1", "체크로직 실패");
    }
} catch (RuntimeException exception) {
    return getResult("1", exception.getMessage());
}
....
return getResult("0", "성공");
@Service
@Transactional(readOnly = true)
public class appVersionService {
	private final AppVersionRepository appVersionRepository;

	public boolean verify(AppVersion.Request request) {
		var appVersion = appVersionRepository.findAppVersion()
				.orElseThrow(() -> new RuntimeException("데이터를 찾을 수 없습니다."));
                ....
		return true;
	}
}

 

- 코드 작성 의도

: request값이 NULL로 전달 온 경우 OR 앱 버전 데이터를 찾을 수 없는 경우

  예외처리 통해 결과값은 실패로 던져주고 사용자 이력은 저장되는 코드를 작성하고 싶었다.

 

- 오류 원인

: RuntimeException은 Unchecked Exception으로 Rollback이 일어나기 때문에

 

- 해결방안1

: @Transactional의 noRollbackFor 속성 사용

  (트랜잭션 전파 속성을 변경하여 해당 서비스때문에 롤백이 일어나지 않도록 noRollbackFor 속성값을 넣는다.)

@Service
@Transactional(readOnly = true)
public class appVersionService {
	private final AppVersionRepository appVersionRepository;

	@Transactional(propagation = Propagation.MANDATORY, noRollbackFor = RuntimeException.class)
	public boolean verify(AppVersion.Request request) {
		var appVersion = appVersionRepository.findAppVersion()
				.orElseThrow(() -> new RuntimeException("데이터를 찾을 수 없습니다."));
                ....
		return true;
	}
}

 

 

- 해결방안2

: Checked Exception 을 만들어서 Rollback 되지 않고 트랜잭션이 commit 되도록 수정

logRepository.save(log);

try {
    if (!appVersionService.verify(request)) {
    	return getResult("1", "체크로직 실패");
    }
} catch (AppVersionVerificationException exception) {
    return getResult("1", exception.getErrorMessage());
}
....
return getResult("0", "성공");
@Service
@Transactional(readOnly = true)
public class appVersionService {
	private final AppVersionRepository appVersionRepository;

	public boolean verify(AppVersion.Request request) throws AppVersionVerificationException {
		var appVersion = appVersionRepository.findAppVersion()
				.orElseThrow(new AppVersionVerificationException(
						ErrorCode.DATA_NOT_FOUND, "데이터가 존재하지 않습니다."));
                ....
		return true;
	}
}
@Getter
public class AppVersionVerificationException extends Exception {
	private final ErrorCode errorCode;
	private final String errorMessage;

	public AppVersionVerificationException(ErrorCode errorCode) {
		super(errorCode.getMessage());
		this.errorCode = errorCode;
		this.errorMessage = errorCode.getMessage();
	}

	public AppVersionVerificationException(ErrorCode errorCode, String message) {
		super(message);
		this.errorCode = errorCode;
		this.errorMessage = message;
	}

	public AppVersionVerificationException(String message) {
		this(ErrorCode.BAD_REQUEST, message);
	}
}
@Getter
@AllArgsConstructor
public enum ErrorCode {
	// Common
	BAD_REQUEST(400, "C001", "bad request"),
	DATA_NOT_FOUND(400, "C002", "data not found");

	private final int status;
	private final String code;
	private final String message;
}

 

 

저는 해결방안2로 적용하였습니다.

 

- 참고사이트

https://techblog.woowahan.com/2606/

 

응? 이게 왜 롤백되는거지? | 우아한형제들 기술블로그

{{item.name}} 이 글은 얼마 전 에러로그 하나에 대한 호기심과 의문으로 시작해서 스프링의 트랜잭션 내에서 예외가 어떻게 처리되는지를 이해하기 위해 삽질을 해본 경험을 토대로 쓰여졌습니다.

techblog.woowahan.com

https://devlog-wjdrbs96.tistory.com/351

 

[Java] Checked Exception vs Unchecked Exception 정리

체크 예외와 언체크 예외(Checked, Unchecked Exception) 자바의 예외는 크게 3가지로 나눌 수 있습니다. 체크 예외(Checked Exception) 에러(Error) 언체크 예외(Unchecked Exception) 자바에서 에러, 예외 관련된 클래

devlog-wjdrbs96.tistory.com

https://mangkyu.tistory.com/269

 

[Spring] 스프링의 트랜잭션 전파 속성(Transaction propagation) 완벽하게 이해하기

아래의 내용은 김영한님의 디비 접근 기술 2편 강의와 토비의 스프링 등을 바탕으로 정리한 내용입니다. 1. 트랜잭션의 시작과 종료 및 전파 속성(Transaction Propagation) [ 트랜잭션의 시작과 종료 ]

mangkyu.tistory.com