ParsingMenu
클래스의 saveEblockMenu()
메서드에 있는deleteAllByRestaurantPropertyId()
메서드가 해당 에러로 작동하지 않았다.
해당 메서드는 JPA 커스텀 메서드다.
분명히saveEblockMenu()
에 @Transactional
을 선언했는데 동작하지 않는다.. 왜 이럴까?
답은 @Transactional
의 동작 원리에 있었다.
위의 saveEblockMenu()
메서드는 (1)getDataAndSaveToDatabase()
-> (2)getDataAndSaveToDatabaseEblock()
-> (3)saveEblockFileName()
-> (4)saveEblockMenu()
순으로 호출된다.
근데 나는 맨 처음 호출하는 메서드인 (1)getDataAndSaveToDatabase()
에 @Transactional
를 추가하지 않았다.
호출하는 메서드에 @Transactional
이 적용되어 있지 않으니까 당연히 하위 메서드들도 적용(전파)되지 않는다.
따라서 (1)getDataAndSaveToDatabase()
메서드를 트랜잭션으로 감싸주는 프록시 객체가 만들어지지 않고, EntityManager
가 존재하지 않는다는 에러가 난 것이다.
그런데 이상하다. 중간에 (3)saveEblockFileName()
에서도 JPA의 save를 사용하는데 에러가 나지 않고 왜 (4)saveEblockMenu()
의 delete에서만 에러가 날까?
해당 메서드 내용이다. 여기서도 EntityManager
가 필요한 save를 사용하고 있다. 그러나 이 부분에서는 에러가 나지 않는다.
한 번 JpaRepository
의 defalut 구현체인 SimpleJpaRepository
를 살펴보자.
@Transactional
이 붙어있다. 따라서 에러가 나지 않았던 것이다.
이것이 무슨 소리냐 하면..
나는
(1)getDataAndSaveToDatabase()
-> (2)getDataAndSaveToDatabaseEblock()
-> (3)saveEblockFileName()
-> (4)saveEblockMenu()
순으로 메서드를 호출했다.
그렇다면 내가 호출한 메서드는ParsingMenu::getDataAndSaveToDatabase()
이며 이 메서드는 트랜잭션으로 감싸져 있지 않다. 따라서 영속성 컨텍스트가 필요한 작업이 있으면 에러가 날 것이다.
하지만 (3)saveEblockFileName()
에서는 영속성 컨텍스트가 필요한 작업인 save를 사용해도 에러가 나지 않았다. 그 이유는 (3)saveEblockFileName()
에서 JpaRepository::save()
를 호출했기 때문이다.
주의할 점은 ParsingMenu
의 메서드를 호출한게 아니다. JpaRepository
구현체의 save()
메서드를 호출했다. 그리고 이 메서드는 @Transactional
이 적용되어 있다. 따라서 트랜잭션으로 감싸진 프록시 객체를 사용할 것이다. 당연히 에러가 나지 않는다.
그런데 (4)saveEblockMenu()
의 deleteAllByRestaurantPropertyId()
는 내가 만든 커스텀 메서드다. 이 메서드는 나중에 동적 프록시에 의해 분석되어 구현되고 JpaRepository::deleteAllByRestaurantPropertyId()
로 실행된다. 하지만 중요한건 이 메서드엔 @Transactional
이 자동으로 적용되지 않는다는 점이다.
따라서 해당 메서드에 @Transactional
을 적용시키면 (1)getDataAndSaveToDatabase()
에 @Transactional
을 적용시키지 않아도 해결된다.
내 상황에서는 해결 방법이 세 가지가 있을 것 같다.
@Transactional
를 적용시켜 전파시킨다.@Transactional
를 적용시킨다.나는 3번을 택했다.
트렌젝션을 적용하지 않아도 잘 작동하는 기본 메서드에 대해 의문을 갖고 있었는데 정말 감사합니다.