작성
·
81
0
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.
1. 강의 내용과 관련된 질문을 남겨주세요.
2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.
(자주 하는 질문 링크: https://bit.ly/3fX6ygx)
3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.
(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)
질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.
=========================================
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오)
예
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)
예
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)
예
[질문 내용]
안녕하세요 영한님
이번에 트랜잭션 적용하면서 @Transactional(readOnly = true)
가 붙은 조회 메서드 내부에 @Transactional(propagation = Propagation.REQUIRES_NEW)
가 붙은 외부 클래스의 메서드가 사용 될 때
물리 트랜잭션이 분리되었고 각각 다른 트랜잭션임에도
변경감지가 적용안되는 부분이 궁금했습니다.
아래는 궁금한 부분에 대한 테스트를 진행한 코드입니다.
@Service
@RequiredArgsConstructor
public class TeamService {
private final TeamRepository teamRepository;
private final TeamExternalService externalService;
@Transactional(readOnly = true)
public Team findTeamWithNameChangeExternal(Team team, String name) {
Team findTeam = teamRepository.findByName(team.getName());
externalService.changeName(findTeam, name);
return findTeam;
}
}
@Service
@RequiredArgsConstructor
public class TeamExternalService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void changeName(Team findTeam, String name) {
findTeam.setName(name);
}
}
@Test
void findTeamWithNameChangeExternal() {
Team team = new Team("team");
Team savedTeam = teamRepository.save(team);
teamService.findTeamWithNameChangeExternal(savedTeam, "changeName");
Team findTeam = teamRepository.findByName("changeName");
assertThat(findTeam.getName()).isEqualTo("changeName");
}
같은 클래스에서 내부 호출 할 경우에는 REQUIRES_NEW
를 적용하더라도 같은 프록시 객체로 해당 메서드가 호출된 상위 트랜잭션의 옵션을 그대로 따라 가기 때문에 변경 감지가 적용안된다고 생각했어요
하지만 이와 다른 외부 클래스의 분리된 트랜잭션에서는 변경 감지가 일어나는 것이 정상이라고 생각했는데
왜 변경 감지가 일어나지 않는 것일 까요 ..?
아래는 궁금해서 디버그를 찍어봤어요
보니가 두 트랜잭션의 번호가 Transaction@13592로 같고
REQUIRES_NEW
가 적용된 트랜잭션의 경우에는 oldTrasactionInfo 이라는 정보를 갖고 해당 메서드를 실행하는 @Transactional(readOnly = true)
옵션이 적혀있더라구요 ??
그렇다면 내부 호출이 아닌 외부 호출이라도
readOnly = true
로 조회 성능을 챙기면서 변경 감지를 사용할 순 없는 것인가요 ??
답변 2
2
안녕하세요. HH님
스프링과 함께 JPA를 사용할 때 JPA의 영속성 컨텍스트는 트랜잭션 범위에 맞추어집니다.
REQUIRES_NEW를 사용하면 완전히 새로운 트랜잭션이 하나 생기게 됩니다. 그리고 거기에 맞추어 영속성 컨텍스트도 새로 생깁니다.
결과적으로 REQUIRES_NEW 트랜잭션 범위 안에서 엔티티를 새로 조회하고, REQUIRES_NEW의 범위 안에서 엔티티를 수정해야 변경 감지가 일어납니다.
감사합니다.
0
안녕하세요, 인프런 AI 인턴입니다.
HH님의 질문에 대해 답변 드리겠습니다.
질문하신 내용은 Spring의 트랜잭션 전파(propagation)에 대한 부분과 트랜잭션 범위에서의 변경 감지(JPA의 영속성 컨텍스트 동작 방식)에 관련된 사항입니다.
@Transactional(readOnly = true)
가 설정된 메서드 내에서 @Transactional(propagation = Propagation.REQUIRES_NEW)
가 붙은 다른 빈의 메서드를 호출할 때, 본질적으로 두 메서드는 서로 다른 트랜잭션을 생성하게 됩니다. REQUIRES_NEW
옵션은 현재 실행 중인 트랜잭션이 있더라도 새로운 트랜잭션을 시작하도록 합니다. 그렇기 때문에, 여기서는 readOnly = true
로 설정된 조회용 트랜잭션과 별개로 새로운 트랜잭션이 시작되어 변경 작업이 가능합니다.
하지만, 실제로 변경 감지가 일어나지 않는다면, 몇 가지 가능한 원인을 고려할 수 있습니다.
트랜잭션 경계: @Transactional(propagation = Propagation.REQUIRES_NEW)
가 적용된 메서드가 실제로 새로운 트랜잭션을 시작했는지 확인합니다. 본문에서 언급한 것처럼, 동일 트랜잭션 번호(Transaction@13592)와 oldTransactionInfo
를 보니 같은 트랜잭션 내에서 실행되고 있는 것으로 보입니다. 이 경우, 트랜잭션 전파 동작이 예상대로 작동하지 않는 것일 수 있습니다.
프록시 대상 메서드 호출: Spring의 AOP 기반이 트랜잭션 관리는 프록시를 통해 이루어집니다. 따라서, 한 클래스 내에서 this.someMethod()
와 같은 방식으로 자신의 다른 메서드를 직접 호출하면, 프록시를 거치지 않게 됩니다. 하지만 여기서는 외부 클래스를 호출하고 있으므로, 이 부분은 문제가 되지 않습니다.
JPA 영속성 컨텍스트와 세션: JPA에서는 변경 감지(dirty checking)를 위해 엔티티를 영속성 컨텍스트에 보관합니다. 트랜잭션이 종료될 때, 변경된 엔티티를 데이터베이스에 반영(flush)합니다. REQUIRES_NEW
로 새 트랜잭션을 시작하면 해당 트랜잭션에 대한 새로운 영속성 컨텍스트가 생기므로, 이 컨텍스트 내에서의 변경 사항이 반영되어야 합니다.
질문하신 상황에서 변경 감지가 일어나지 않는 것은 트랜잭션 관리와 영속성 컨텍스트의 범위 설정 등에 문제가 있을 가능성이 있습니다. Spring의 트랜잭션 관리와 JPA의 영속성 컨텍스트 동작 방식을 다시 검토해보시는 것이 좋겠습니다.
외부 호출에서 readOnly = true
와 변경 감지를 모두 사용하려면, 기본적으로 REQUIRES_NEW
와 같이 새로운 트랜잭션에서 변경 작업을 수행하는 방식으로 접근하는 것이 맞습니다. 따라서, 문제의 원인이 트랜잭션 관리 방식의 잘못된 설정 혹은 기대한 대로 트랜잭션 전파가 이루어지지 않은 것일 가능성이 높습니다.