본문 바로가기

Back-End/Spring

Spring DeleteAllBy...In 호출시 에러 ( TransactionRequiredException )

문제 상황 : deleteAllByIdxIn 호출 시 entitymanager가 왜 없을까?

JPA OSIV라면 기본적으로 트랜잭션 범위는 서비스 단까지 있을테고, entity manager는 생성됐을 것이다. 그런데 왜 아래와 같은 에러가 났을까??

javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call
@Service
@AllArgsConstructor
public class ItemServiceImpl implements ItemService {

    private final ItemRepository itemRepository;

    @Override
    public Item findById(long itemId) {
        final Optional<Item> itemOptional = itemRepository.findByIdx(itemId);

        return itemOptional.get();
    }


    @Override
    public void delete(long itemId) {

        final int num = itemRepository.deleteAllByIdxIn(Lists.newArrayList(itemId));

        System.out.println(num);
    }
}

 JPA EntityManager는 누가 열까?

 

아래 화면들은 DELETE API를 호출 시에 Entity Manager가 생성되는 코드와 breakpoint이다.

DELETE API 호출시 Entity Manage가 생기는 부분 debugging

public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAccessor implements AsyncWebRequestInterceptor {

// ...
	
    @Override
	public void preHandle(WebRequest request) throws DataAccessException {
		String key = getParticipateAttributeName();
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		if (asyncManager.hasConcurrentResult() && applyEntityManagerBindingInterceptor(asyncManager, key)) {
			return;
		}

		EntityManagerFactory emf = obtainEntityManagerFactory();
		if (TransactionSynchronizationManager.hasResource(emf)) {
			// Do not modify the EntityManager: just mark the request accordingly.
			Integer count = (Integer) request.getAttribute(key, WebRequest.SCOPE_REQUEST);
			int newCount = (count != null ? count + 1 : 1);
			request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST);
		}
		else {
			logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewInterceptor");
			try {
				EntityManager em = createEntityManager();
				EntityManagerHolder emHolder = new EntityManagerHolder(em);
				TransactionSynchronizationManager.bindResource(emf, emHolder);

				AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(emf, emHolder);
				asyncManager.registerCallableInterceptor(key, interceptor);
				asyncManager.registerDeferredResultInterceptor(key, interceptor);
			}
			catch (PersistenceException ex) {
				throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
			}
		}
	}

}

deleteAllByIdxIn 동작 방식

  1. EntityManager opened
  2. SELECT 쿼리 생성
  3. EntityManager closed
  4. DELETE 쿼리 예외 발생

EntityManager 없다는 에러를 해결하기 위해 @Transactional 붙여서 로그보기

JpaRepository에서 기본적으로 함수들은 @Transactional이 붙은줄 알았는데 아닌가....?

 

SimpleJpaRepository 코드를 살펴보자

@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {

내가 알고 있던 대로 SimpleJpaRepository에는 Transactional 어노테이션이 붙어있다. 하지만, 구현 함수중에 deleteAllBy는 찾을 수가 없었다.

	@Override
	@Transactional
	@SuppressWarnings("unchecked")
	public void delete(T entity) {
    
    
    @Transactional
	@Override
	public void deleteAll(Iterable<? extends T> entities) {

즉 deleteAllBy...으로 사용하면 트랜잭션 어노테이션이 기본적으로 먹히지 않는것 같다. 그래서 아래와 같이 했더니 해결은 되긴 했다. 좀더 깊은 이해가 필요할 것 같다....

 

public interface ItemRepository extends JpaRepository<Item, Long> {

    @Transactional
    int deleteAllByIdxIn(List<Long> ids);

}