JPA에서 제공하는 Repository로 delete 관련 메서드를 만들었다.
public interface InstagramRepository extends JpaRepository<Instagram, Long> {
void deleteByPlaceId(Long placeId);
}
그리고 관련 로직을 수행하면 예외가 발생했다.
@Transactional
public doSomething(Instagram instagram, Place place) {
delete(instagram, place);
save(instagram);
}
private delete(Instagram instagram, Place place) {
if (어떤 조건이라면) {
instagramRepository.deleteByPlaceId(crawlingPlace.getId());
}
}
대략적으로 이런 상황이었다. 그리고 테스트를 돌리면 deleteByPlaceId 에서 Transaction이 있는 EntityManager가 없다는 예외가 발생했다.
No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call; nested exception is javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call
doSomething 메서드에서 트랜잭션을 걸어줬으면 전파되야 하는 것 아닌가?
delete 메서드는 외부에서 사용하지 않기 때문에 private으로 막아놨다. 하지만 private 메서드는 트랜잭션이 걸리지 않는다. 그래서 트랜잭션이 걸려있지 않아 트랜잭션이 있는 EntityManager가 없다는 예외가 발생한 것이다.
public delete(Instagram instagram, Place place) {
if (어떤 조건이라면) {
instagramRepository.deleteByPlaceId(crawlingPlace.getId());
}
}
위와 같이 private에서 public으로 바꾸면 트랜잭션이 전파되면서 예외가 발생하지 않는다. 하지만 이런 식으로 처리한다면 예상치 못한 상황에서 예외가 발생할 수 있다. 부모에 항상 트랜잭션을 달아주는 것은 번거롭다.
public interface InstagramRepository extends JpaRepository<Instagram, Long> {
@Transactional
void deleteByPlaceId(Long placeId);
}
그래서 위와 같이 조회가 아닌 저장,수정,갱신 로직에는 트랜잭션을 붙여주는 게 좋다.
JpaRepostiroy의 실제 구현체인 SimpleJpaRepository 속을 들여다 보면 save, delete 관련 로직에는 트랜잭션이 붙어있다. 실제 구현체에서 제공하는 save,delete 메서드가 아니라면 트랜잭션을 붙여주자!