Transaction strategies: Understanding transaction pitfalls

@Transactional(readOnly = true, propagation=Propagation.REQUIRED)
public long insertTrade(TradeData trade) throws Exception {
   // 어떤 코드
}

위 코드에 있는 insertTrade를 실행행하면 어떤 결과가 발생할까요?

A. read-only connection 예외가 발생한다.
B. 데이터를 추가하고 커밋한다.
C. read-only가 true라서 아무것도 하지 않는다.

당연히 A일 것 같은데... // 어떤 코드 부분이 JDBC 코드일 경우이고 JPA나 하이버네이트 같은 ORM 코드면 propagation 설정 REQUIRED가 모든 것을 재정의해서 새로운 트랜잭션을 시작하고 read-only 플래그가 없는 것 처럼 동작하게 된다네요... @_@

지금은 넘 졸려서 낼 자세히 읽어봐야겠습니다.

따라서 읽기 전용 매서드의 경우 다음과 같이 SUPPORTS 프로퍼게이션 모드를 사용하는게 타당하다고 합니다. 왜냐면 보통 다음과 같이 읽기 전용 매서드를 설정하는데..

@Transactional(readOnly = true)
public TradeData getTrade(long tradeId) throws Exception {
   return em.find(TradeData.class, tradeId);
}

이 때 기본 프로퍼게이션이 REQUIRED 모드기 때문에 매번 새로운 트랜잭션을 만들어 사용하고 사용하는 DB에 따라서는 불필요한 읽기 롹까지 사용해서 데드락을 발생시킬 수도 있다고 하기 때문입니다. 따라서..

@Transactional(readOnly = true, propagation=Propagation.SUPPORTS)
public TradeData getTrade(long tradeId) throws Exception {
   return em.find(TradeData.class, tradeId);
}

이렇게 SUPPORTS로 변경하여 기존 트랜잭션이 있으면 사용하고 없으면 사용하지 않도록 하거나..

public TradeData getTrade(long tradeId) throws Exception {
   return em.find(TradeData.class, tradeId);
}

요렇게 읽기 전용인 경우에는 아예 @Transactional 애노테이션을 아예 사용하지 않는게 나아보입니다. 굳이 원자화 할 것도 없고~ Isolation level만 적당선에서 타협한다면 롹을 걸 일도 없고~


REQUIRES_NEW  사용시 주의 할 것.

@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception {...}

@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updateAcct(TradeData trade) throws Exception {...}

이런 매서드와 트랜잭션 설정이 있을 때

@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception {
   em.persist(trade);
   updateAcct(trade);
   //exception occurs here! Trade rolled back but account update is not!
   ...
}

이런 식으로 코딩하면... updateAcct() 이후에 에러가 발생하면 updateAcct는 쿼리가 날아가는데 나머지 내용은 롤백 되는 현상이 발생할 수 있죠. 왜냐면 매번 새로운 트랜잭션을 만들기 때문에 insertTrade를 실행하는 트랜잭션과 updateAcct를 실행하는 트랜잭션이 별개기 때문입니다.

뭐~ 이건 쉽네요. 걍 REQUIRED로 쓰거나 MANDATORY를 쓰면 됩니다.

롤백과 관련해서는 checked exception에 대비해야 한다.

@Transactional(propagation=Propagation.REQUIRED)
public TradeData placeTrade(TradeData trade) throws Exception {
   try {
      insertTrade(trade);
      updateAcct(trade);
      return trade;
   } catch (Exception up) {
      //log the error
      throw up;
   }
}

이런 코드가 과연 안전할까? 아니요. 왜냐면 저 코드에서 만약 updateAcct() 매서드 처리 도중에  checked exception이 발생하면 insertTrade() 매서드는 그대로 실행하고 예외도 그냥 던지고 말기 때문에 데이터가 불안전한 상태가 될 것입니다. 따라서 checked exception에 대비해서..

@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public TradeData placeTrade(TradeData trade) throws Exception {
   try {
      insertTrade(trade);
      updateAcct(trade);
      return trade;
   } catch (Exception up) {
      //log the error
      throw up;
   }
}

이런 식으로 하는게 좋겠습니다.