Do Something

[RuntimeException] @Transactional과 커스텀 Exception / Application exception overridden by commit exception 본문

Spring

[RuntimeException] @Transactional과 커스텀 Exception / Application exception overridden by commit exception

뭐라도해야겠다 2023. 7. 31. 19:59

어노테이션 @Transactional을 붙인 서비스에서 에러가 났다. 

원하는 상황은 다음과 같다

 

1. Project를 만들때는 (ProjectDto, userList(Long 배열), Owner) 세가지가 필요하다. 

2. userList들을 register이라는 엔티티로 등록한다.

    이때 userList의 아이디중 하나라도 디비에 없으면 ( user에 foreign key로 걸려있음) @Transactional 을 통해서 롤백한다. 

 

여기서 나는 userList에 없는 아이디가 존재하면 RegisterMakeException이나 UserNotFoundException을 주려고 했다. 

아무생각 없이 그 밑에 에러가 RuntimeException이나 PropertyValueException등 Exception을 상속받아서 하려고 했다.

RegisterException

그리고 일부러 틀린 아이디를 넣어서 테스트를 돌렸는데 Exception을 잡는게 아니라 에러가 발생했다. 

Application exception overridden by commit exception

이라는 에러가 떳다. 

 

인터넷에 검색해보니까 어플리케이션 단위의 문제라고 한다. 

이상하다? 

왜 어플리케이션 단위의 에러가 나고 디비에도 문제가 생겼다고 하는지 몰랐다. 

 

결론적으로는 @Transactional 의 롤백 규칙에 대한 무지가 문제였다. (처음해서... 하하하)

 

@Transactional을 클래스나 매서드에 사용하면 해당 메서드나 클래스가 시작할 때 트랜잭션을 실행한다.

기본적으로 @Transactional은 두가지를 가지고 롤백을 한다. 

RuntimeException, Error이다. 

@Transactional을 단 매서드에서 error가 나거나 RuntimeException을 상속받은 exception이 발생하면 트랜잭션을 롤백한다는 말이다. 

 

다시말하면, RuntimeException을 catch해서 다른 Exception을 날렸을 때는, 다른 Exception이 RuntimeException을 덮어버려서 함수가 끝나버리고, 트랜잭션을 시작했는데 끝나지는 않아서 어플리케이션 단위에서 에러가 발생하게 된다. 

 

나는 이에 대한 해결책으로 RuntimeException을 상속하는 RegisterMakeException을 만들어서 RuntimeException을 받아 원하는 메세지와 묶어 처리한 뒤, @Transactional에 rollbackFor이라는 속성을 사용해서 내가 만든 커스텀 예외도 롤백처리를 명시적으로 해주었다.

그랬더니 해결이 됬다. 

 

프로젝트를 하면서 AOP와 ExceptionHandler을 이렇게 세분화해서 하는게 처음인데 이것도 생각보다 골치아픈거 같다.

그래도 배우는거같아서 재밋넹