[org.hibernate.persistentobjectexception: detached entity passed to persist] @Transactional의 중요성...
이번에 새로 진행하고 있는 머니케쥴 프로젝트에서 너무 어이없는 실수를 했다.
발생한 에러
org.hibernate.persistentobjectexception: detached entity passed to persist
뭐가 문제였지?
구글링을 해보니 cascade 설정 시에 CascadeType.ALL 로 설정하면 많이 발생하는 문제라고 하던데 이걸 아무리 건들여봐도 문제가 해결되지 않았었다..
문제는 왜 서비스단에서 member를 찾아야 하지? 라는 의문 부터 시작했다. 기존 코드는
@Transactional
public Payment saveDeposit(Long memberId, String memo, Long amount, String type) {
Member findMember = memberRepository.findById(memberId)
.orElseThrow(NotFoundMemberException::new);
Payment deposit = Deposit.of(findMember, memo, amount, type);
return paymentRepository.save(deposit);
}
이렇게 되어 있었는데 매번 습관적으로 id를 넘겨서 서비스단에서 member를 찾았었는데, 그냥 Controller단에서 member를 찾아서 넘겨주면 안될까? 라는 생각을 했다. 그래서 코드를
@Transactional
public Payment saveDeposit(Member findMember, String memo, Long amount, String type) {
Payment deposit = Deposit.of(findMember, memo, amount, type);
return paymentRepository.save(deposit);
}
로 바꿨는데 여기부터 문제였다.. JPA 영속성 컨텍스트가 Transaction단위로 유지된다는 사실을 간과한 것...
이렇게 넘겨준 member는 영속성 컨텍스트의 관리 범위에서 벗어난 상태, 준영속 상태로 되어 있었던 것이었다.
그래서 member에 대한 정보를 찾지 못했고 기존에 제대로 동작하던 코드들이 말썽을 부리게 된 것이었다..
여기서 크게 문제를 못 느꼈던게 테스트 코드를 돌려보면 잘 동작했기 때문이었다. 바꾸고 돌려봤을 때, 테스트가 실패했다면 안되는구나! 하고 알았을텐데 테스트는 각 테스트가 Transaction안에서 돌기 때문에 member를 밖에서 넘겨줘도 영속성 컨텍스트가 잘 관리하는 상태였기에 문제가 안생기는 것이었다...
여기서 중요한 점은 내가 JPA의 OSIV(Open Session In View) 설정은 꺼놓았기 때문에 발생하는 문제라는 것이다. 만약에 이를 켜놓았다면 영속성 컨텍스트가 Transactional 밖 Controller단까지 유지되어 준영속 상태로 존재하지 않았을 것이다. 하지만 OSIV는 너무 오랜시간동안 데이터베이스와 커넥션을 유지하기 때문에 실무에서는 끄는 방향이 좋다고 한다. 즉 영속성 컨텍스트는 Transaction 단위에서만 동작하고 조회 기능도 Transactional(readOnly = true) 어노테이션을 부여하여 활용하는 방식으로 하는 것이 좋을 것 같다.
결론
최근 Transaction을 너무 간과하고 코드를 작성해 문제가 여럿 생기는 것 같다.. 좀 더 주의를 기울이고 영속성 컨텍스트 관리를 유의해서 작성해야겠다.