DevelopmentTools/jpa

[JPA] CASCADE.ALL로 인한 버그 발생 및 해결법

수짱수짱 2023. 9. 25. 19:26

해당 게시글은 "프로그래머스 데브코스 4기"의 팀 내 프로젝트 기록용으로 TECH BLOG에 직접 작성한 글입니다.


문제 상황

상품에 대한 review를 작성하는 api 기능 테스트를 진행하던 와중이었다. (/products/review, 사진의 서비스 메서드 호출)

아래와 같은 에러가 발생하였고 이를 해결했던 과정을 기록하고자 한다.

2023-09-21T17:55:12.524+09:00  WARN 81332 --- [nio-8080-exec-7] c.d.k.g.e.GlobalExceptionHandler         : UnexpectedException Occurs : detached entity passed to persist: com.devcourse.kurlymurly.module.user.domain.User

해당 문제는 User 가 이미 DB에 저장이 되어 있어 영속성 문제로 인한 문제 상황이었다.

 

원인

UserService의 login에서 JwtTokenProvider 메서드 호출

 

createToken에서 CustomUserDetailService의 getAuthentication 메서드 호출

 

loadUserByUsername에서 UserRepsotiroy 호출 ⇒ User 영속성 생성

 

User가 이미 login → JwtTokenProvider → UserDetailService → UserRepository 에서 영속성이 있었고 그 상태에서 관리가 이어지지 않아 준영속 상태였기 때문이다 (위 사진)

따라서 CASCASDE.ALL이 User를 MERGE 해야할지 PERSIST 해야할지 JPA는 모르는 것이고 그에 따라 위와 같은 문제 상황이 발생하게 되는 것이다.

 

해결

1️⃣ (채택!)

원래는 User의 cascade 속성이 ALL이었다.

 

 

2️⃣

원래는 @Transaction이 달리지 않았다.

 

 

2번의 경우 Transaction으로 User의 영속성이 CustomUserDetailService에서 Detach되지 않고 쭉 이어갈 수 있도록 해주는 방법이다.

만약 ProductFacade에 Transaction을 걸어준다면 트랜잭션이 상위에 종속되는 성질에 따라 detach 되었던 User가 다시 종속되게 될 것이다.

하지만, Facade에 Transaction을 거는 것은 바람직하지 않은 모습이다. (=우리 팀 정책상 위배)

 

 

따라서, 우리는 해결방법으로 1번을 채택하였다.

CASCADE.MERGE라면 PERSIST냐 MERGE냐 고민하지 않고 MERGE로 문제를 해결할 수 있게 된다.

주의해야 할 것은 MERGE했다고 준영속 상태의 User와 Review에 연관관계로 있는 User가 같지않다. 다른 객체지만 값만 같은 상태이다.

무슨말이냐면 토큰에서 다룬 User와 ReviewCreateUser는 값만 같고 다른 객체라는 의미이다.

 

 

부록: Product는 왜 영속성 문제가 발생하지 않는가?

Product도 User와 똑같이 Review에 CasacadeType이 ALL이며 연관관계가 걸려있는데 왜 User에만 문제가 발생하는 것일까 ?? 의문이 들었다.

 

리뷰등록에서 필요한 Product 객체는 productQuery로 부터 얻어온다.

ProductQuery는 readOnly가 걸려있어서 영속화가 되지않고 오직 Product 데이터만 넘겨준다.

(마찬가지로 readOnly를 사용하면 성능이 향상되는이유가 영속성 없이 데이터만 바로 넘겨주기 때문인거다)

(트랜잭션은 상위 트랜잭션에 종속되어 합쳐져 동작하기 때문에 ProductQuery의 findProductByIdOrThrow메서드도 readOnly로 동작하는 것이다)

 

그렇기 때문에, CASCADE.ALL이 걸려있어도 Product를 영속화해주고 저장하는데에 문제가 없는 것이다.

 


Reference