• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

@Transaction 전파 관련 문의

21.09.27 17:35 작성 조회수 524

1

안녕하세요 김영한 팀장님!

 

실습한 MemberRepository의 구현체로 SimpleJpaRepository가 생성되며, 내부의 findById 메서드가 @Transactional(readOnly)이 적용되어 있고 기본 설정이 "Propagation.REQUIRED"이기 때문에 부르는 쪽의 Transaction이 전파되는 것으로 이해했습니다.

 
@Transactional
@GetMapping("/tx/{id}")
public void findMember3(@PathVariable("id") Long id) {
Member member1 = memberRepository.findById(id).get();
Member member2 = capsule(id);

System.out.println(member1);
System.out.println(member2);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public Member capsule(Long id) {
return memberRepository.findById(id).get();
}

하지만 다음과 같이 컨트롤러 메서드 "findMember3"에 @Transactional을 걸고, memberRepository의 findById를 수정할 수 없으므로 capsule 메서드로 감싸서 member를 조회해봤는데요. propagation을 new로 설정했음에도 출력 결과가 같은 인스턴스를 가리키는 것으로 나왔습니다.select query도 하나만 발생했는데, 이 경우에도 같은 영속성 컨텍스트를 공유하게 되는 건가요?

 

 

답변 5

·

답변을 작성해보세요.

3

dooong dooong님 잘 정리하셨네요^^

조건 2 : osiv on, 외부 메서드에 @Transactional 무 -> 같은 member 조회

이 경우는 코드가 이렇게 동작합니다.

    @GetMapping("/em/{id}")
// @Transactional
public void findMember5(@PathVariable("id") Long id) {

Member member1 = memberRepository.findById(id).get();
System.out.println("member1 : " + member1);

testComponent.capsule(id);
}

여기에서 memberRepository.findById를 할 때 해당 메서드에서 OSIV가 동작하면서 영속성 컨텍스트에 회원이 올라갑니다. 이미 OSIV와 영속성 컨텍스트가 동작하고 있습니다.

 

@RequiredArgsConstructor
@Component
public class TestComponent {

private final MemberRepository memberRepository;
private final EntityManager em;

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void capsule(Long id) {

Member member2 = em.find(Member.class, id);
System.out.println("member2 = " + member2);
}
}

그리고 여기에서 REQUIRES_NEW라고 하면 기존 트랜잭션이 없기 때문에 그냥 트랜잭션을 하나 시작하는 것입니다. 이 경우 REQUIRES_NEW가 사실 의미가 없습니다. 기존 트랜잭션이 없기 때문에 단순히 REQUIRED와 같은 효과입니다. 이 트랜잭션은 OSIV에 의해 이미 동작하고 있는 영속성 컨텍스트에 참여하게 됩니다.

정리하면 OSIV가 켜져 있는 경우

1. 트랜잭션은 기존에 생성된 영속성 컨텍스트에 참여합니다.

2. 트랜잭션안에 또 다른 트랜잭션이 REQUIRES_NEW로 생성되면 이 경우 별도의 영속성 컨텍스트에 새로 생성된 트랜잭션이 참여합니다.

감사합니다.

 

1

안녕하세요. dooong dooong님

좋은 질문입니다.

JPA 활용2의 OSIV와 성능 최적화부분을 다시 복습해보시고, 실험해보시면 원하시는 답을 찾으실 수 있을거에요.

감사합니다.

 

1

안녕하세요. dooong dooong님

스프링 AOP에서는 내부 메서드 호출시 AOP가 적용되지 않습니다.

클래스를 분리해서 호출해주세요.

감사합니다.

0

jh님의 프로필

jh

질문자

2021.10.06

답변해주셔서 감사드립니다!

osiv를 참고하여 on/off 조건으로 다음과 같이 테스트 결과를 정리해보았습니다. 추가 질문은 맨 마지막에 작성하였습니다.

 

< 내부 메서드(전 질문과 동일) >

@RequiredArgsConstructor
@Component
public class TestComponent {

private final MemberRepository memberRepository;
private final EntityManager em;

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void capsule(Long id) {

Member member2 = em.find(Member.class, id);
System.out.println("member2 = " + member2);
}
}

 

< 외부 메서드(전 질문과 동일) >

    @GetMapping("/em/{id}")
// @Transactional
public void findMember5(@PathVariable("id") Long id) {

Member member1 = memberRepository.findById(id).get();
System.out.println("member1 : " + member1);

testComponent.capsule(id);
}

 

< 공통 조건 : 내부 메서드의 @Transactional은 항상 requires_new로 적용 >

 

1) osiv on(영속상태 유지(단, 수정 불가능). 즉 트랜잭션 범위 외에서도 영속 상태 유지 o)

조건 1 : 외부 메서드에 @Transactional 유 -> 다른 member 조회

조건 2 : 외부 메서드에 @Transactional 무 -> 같은 member 조회

 

2) osiv off(트랜잭션 종료 시 영속성 컨텍스트를 닫는다. 즉 트랜잭션 범위 밖에서는 영속상태 유지 x)

조건 3 : 외부 메서드에 @Transactional 유 -> 다른 member 조회

조건 4 : 외부 메서드에 @Transactional 무 -> 다른 member 조회

 

조건 1, 3의 경우 osiv on/off 여부에 상관 없이 외부에 @Transactional이 있기 때문에 컨트롤러 메서드 레벨에서 transaction이 시작되며, 내부의 requires_new가 잘 동작하여 각각 다른 member가 조회됩니다.

 

조건 2,4 경우 osiv on/off 여부에 따라 member 조회 결과에 차이를 보이게 됩니다.

조건 4(osiv off)는 외부 메서드 내의 "repository.findById" 부분이 끝나자마자 영속성이 사라지게 되므로, 내부 메서드의 transaction이 완벽하게 분리가 되어 원하는대로 동작함을 확인했습니다.(내부 @transactional을 default로 설정해도 동일하게 동작)

조건 2(osiv on)는 외부 메서드 내의 "repository.findById" 실행 이후에도 (조회용)영속상태가 외부 메서드 종료시점까지 유지되는 것으로 이해했습니다. 하지만 이러한 조건이 내부 메서드에 적용된 requires_new로 분리가 되지 않는 점이 이해가 잘 가지 않습니다.. ㅠ

0

jh님의 프로필

jh

질문자

2021.10.02

네 답변 감사합니다! 말씀하신대로 진행해서 정리해봤는데 다음과 같은 결과가 나왔습니다.

AOP를 처음 사용해본거라, 조금 헷갈리는 부분이 있어서요~ㅠ 추가 질문 2가지가 있습니다.

 

1) 앞서 질문의 내부에서 호출된 메서드 "capsule"을 별도 클래스(TestComponent)로 분리하였습니다.

@RequiredArgsConstructor
@Component
public class TestComponent {

private final MemberRepository memberRepository;
private final EntityManager em;

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void capsule(Long id) {

Member member2 = em.find(Member.class, id);
System.out.println("member2 = " + member2);
}
}

 

2.1) 호출하는 메서드에 @Transactional 적용 안함

Controller의 "findMember5" 메서드에서 member find를 진행하고, 1)의 "capsule"메서드를 호출하였습니다. 이 때 "findMember5" 메서드에 @Transactional은 적용하지 않았습니다.

    @GetMapping("/em/{id}")
// @Transactional
public void findMember5(@PathVariable("id") Long id) {

Member member1 = memberRepository.findById(id).get();
System.out.println("member1 : " + member1);

testComponent.capsule(id);
}

이 경우, 동일한 member가 호출되었습니다. 간단히 그림으로 보면 다음과 같습니다.

 

2.2) 호출하는 메서드에 @Transactional 적용(주석 제거)

@Transactional을 외부에도 적용했을 경우에는 member1, member2가 다른 결과가 나왔습니다. 이 경우에는 내부 메서드의 @Transactional(REQUIRES_NEW)가 정상적으로 동작한 것으로 이해했습니다.

 

질문 1 : 외부 메서드의 @Transactional 적용 여부에 따라 위와 같은 결과가 나오는데, 이유를 설명해주실 수 있으실까요? 오히려 2.1)의 트랜잭션이 완벽하게 분리가 될 것으로 생각했습니다. 

 

질문 2 :  다른 AOP의 경우에도 @Transacional의 case(내부 @Transactional에만 Requires_new 적용)와 마찬가지로 내부 어노테이션에 다른 기능(옵션)을 부여할 수 있나요?