스프링/6. 스프링 DB-2

60. 트랜잭션 전파 활용 7

sdafdq 2023. 10. 18. 07:41

저번에 만약 try catch로 복구를 시도하려 한다면 여러 고려를 했어야 했다.

근데 사실 간단한 것이 있다.

REQUIRES_NEW 하면 그냥 저 트랜잭션만 분리시킬 수 있기 때문에, 다른 트랜잭션에 영향이 안간다.

 

@Slf4j
@Repository
@RequiredArgsConstructor
public class MemberRepository {
    private final EntityManager em;

    @Transactional
    public void save(Member member){
        log.info("member 저장");
        em.persist(member);
    }

    public Optional<Member> findByUsername(String username){


        return em.createQuery("select m from Member m where m.username = :username", Member.class)
                .setParameter("username", username)
                .getResultList().stream().findAny();
    }
}

 

 

@Slf4j
@Repository
@RequiredArgsConstructor
public class LogRepository {
    private final EntityManager em;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save(Log logMessage){
        log.info("log 저장");
        em.persist(logMessage);

        if(logMessage.getMessage().contains("로그예외")){
            log.info("log 저장 시 예외 발생");
            throw new RuntimeException("예외 발생");
        }
    }

    public Optional<Log> findByMessage(String message){
        return em.createQuery("select l from Log l where l.message = :message", Log.class)
                .setParameter("message", message)
                .getResultList().stream().findAny();
    }
}

LogRepository의 propagation을 REQUIRES_NEW로 설정했다.

이제 원래 트랜잭션 동기화 매니저에 원래 커넥션(트랜잭션)이 있어도 새 트랜잭션을 생성한다.

물론 무작정 새 트랜잭션을 생성하는 게 아니라, 기존 트랜잭션이 있다는 것을 인지한다.

그래서 잠깐 그걸 멈춘다고 표현해야 하나..

그러니까 최근에 만난 REQUIRES_NEW를 먼저 사용한다. 일종의 트리개념도 적용 가능할 듯?

하지만 그렇게 트리 모형으로 까지는 하지 않을 것이다.. 후에 설명

 

여튼 완전 다른 범위의 트랜잭션이라고 보면 된다. 실상은 전 트랜잭션을 잠깐 멈추고 이걸로 시작하는 거지만..

그냥 다른 범위의 트랜잭션이라고 생각하면 좋다.

 

그래서, 여기서 롤백되던 커밋이 되던 여기서만 그런거고, 다른 트랜잭션은 일절 관계 없다.

 

@Transactional
public void joinV2(String username){
    Member member = new Member(username);
    Log logMessage = new Log(username);

    log.info("== memberRepository 호출 시작 ==");
    memberRepository.save(member);
    log.info("== memberRepository 종료 ==");


    try{
        log.info("== logRepository 호출 시작 ==");
        logRepository.save(logMessage);
    }catch (RuntimeException e){
        log.info("log 저장에 실패했습니다.", logMessage.getMessage());
        log.info("정상 흐름 반환");
    }
    log.info("== logRepository 종료 ==");

}

본격적으로 리포지토리를 호출하는 Service의 메소드인데,

놓치면 안되는 것이 아무리 다른 트랜잭션이라고 해도,

코드상에 예외는 올라온다.

그래서 만약 런타임 예외라면, 얘가 따로 처리를 해주지 않으면 예외 때문에 롤백하기로 판단된다.

그래서 저렇게 예외처리를 하여 정상흐름으로 만들어야 한다.

(아니면 회원가입은 되었지만 ~~ 는 처리가 안되었습니다. 고객센터에 문의 하십시오. 이런 거 보내고 싶을 땐 잡아서 우리가 만든 커스텀 에러 같은 거 날려도 될 듯?)

이런 식.

 

 

 

 

보면 저기 예외를 처리하며 정상흐름으로 복구 한다.

 

 

근데, 이게 좋은 방법일까?

아니다. 이러면 한 요청에 커넥션을 2개 쓰게 된다. 2개 가지고 있으면서, 하나는 정지시켜 놓은 상태이므로 굉장히 비효율 적이다.

물론 REQUIRES_NEW 사용해서 굉장히 깔끔해진다면 그렇게 써도 좋겠지만.. 그래서 잘 생각하고 적절하게 써야 한다.

 

그러면 어떤 방법이 있냐면,

그냥 이런 식으로 Facade, 전면에 클래스를 하나 내세워서 처리 하는 것이다.

이렇게 하면, 동시에 2개의 커넥션을 사용하지 않는다.

지금 con2라고 되어있긴 한데, 물리트랜잭션 1에서 끝나고 con1을 반납하고 있기 때문에 con2를 con1이라고 하는 게 더 이해에 도움이 될 것 같다.

 

이렇게 하면 쓸데 없이 노는 커넥션 없이 잘 사용 된다.

 

그래도 REQUIRES_NEW를 쓰면 깔끔해지는 경우가 있으므로, 잘 생각하고 선택하길..

나는 주로 안쓸 것 같기는 함.

'스프링 > 6. 스프링 DB-2' 카테고리의 다른 글

59. 트랜잭션 전파 활용 6  (0) 2023.10.17
58. 트랜잭션 전파 활용 5  (0) 2023.10.17
57. 트랜잭션 전파 활용 4  (0) 2023.10.17
56. 트랜잭션 전파 활용 3  (0) 2023.10.17
55. 트랜잭션 전파 활용 2  (0) 2023.10.17