JPA 변경 감지와 커밋

김영훈·2024년 2월 29일

Area

목록 보기
2/5
post-thumbnail

PR 코드 리뷰 중 이런 피드백이 달렸다.

질문 :

 public void updateManager(Team team, String managerName) {
   if (team.getManager() == null) team.updateManager(managerName);
     else {
     Member member = memberRepository.findByTeamIdAndRoleIsTrue(team.getId())
           .orElseThrow(IllegalArgumentException::new); 
            member.changeRole();
            team.updateManager(managerName);
        }
    }

JPA 의 변경감지를 통해 업데이트를 진행하고 계신것 같아보입니다! 혹시 이부분 정상적으로 동작이 되나요?? 제가 알았던 바로는 변경감지 또한 트랜잭션 안에서 동작하게 되어있어 별도의 트랜잭션 설정이 없다면 동작을 안하는걸로 알고있었는데 제가 잘못알고 있던건지 궁금해서 여쭤봅니다.


나의 답변 :

정말 좋은 피드백 감사드립니다 !! 😊
제가 JPA에 대한 이해도가 낮아서 말하는 내용이 틀릴 수도 있습니다..!!

결론부터 말씀드리자면, updatemanager를 단독으로 사용하는 것이 아니라면,
@Transactional어노테이션을 따로 지정해주지않아도 현재 코드상에서는 작동합니다.
왜냐하면

public void saveMember(SaveMemberRequest request) {  
    Team team = teamService.findTeamByName(request);  
    memberRepository.save(request.toEntity(team));   <- @@here@@
}

이 부분에서 트랜잭션이 일어나고 있고, 트랜잭션이 종료될 때, 커밋을 통해 변화를 감지하고 데이터베이스에 반영하고 있기 때문입니다.

코드 상에는

@Transactional  <- here
public void saveMember(SaveMemberRequest request) {  
    Team team = teamService.findTeamByName(request);  
    memberRepository.save(request.toEntity(team));  
}

이 부분에 @Transcational 처리가 되어있긴한데,

//@Transactional 
public void saveMember(SaveMemberRequest request) {  
    Team team = teamService.findTeamByName(request);  
    memberRepository.save(request.toEntity(team));  
}

이렇게 @Transcational 어노테이션 부분을 주석처리를 하여도 team 에 대한 변경 사항이 저장되는 것을 확인하였습니다..!
image

  1. @Transcational을 주석처리합니다.

image

  1. 기존의 5팀은 매니저가 없는상태입니다.

스크린샷 2024-02-28 173539

  1. 새로운 직원을 등록하며 매니저로 선언합니다.

image

  1. 5팀의 매니저로 등록되었습니다.

저도 이 부분이 이해하기 어려웠는데,

  1. team을 생성하고 teamRepository에 save를 할 때 team엔티티는 영속 상태가 된다.
  2. 영속상태의 Entityteam은 영속성 컨텍스트의 1차 캐시에 자신의 상태를 스냅샷 처럼 남겨놓는다.
    그 후 , memberRepository에 save를 통해 트랜잭션이 시작됩니다.
@Transactional <- @here!!
    public <S extends T> S save(S entity) {
        Assert.notNull(entity, "Entity must not be null");
        if (this.entityInformation.isNew(entity)) {
            this.entityManager.persist(entity);
            return entity;
        } else {
            return this.entityManager.merge(entity);
        }
    }
  1. transcation 종료 (종료시 commit 발생) --> transcation commit --> flush() --> 스냅샷과 엔티티비교 --> 변경사항이 있으면 update 쿼리를 쓰기지연 저장소에 저장 --> 쓰기지연 저장소에 있는 쿼리를 DB에 반영

실제로 쿼리를 확인해보면 team 변경사항이 데이터 베이스에 반영되는시기는 (Update 쿼리가 날아가는 시점은)
member가 저장된 후 인것을 확인할 수 있었습니다.

# teamRepository.findByName()
Hibernate: 
    select
        t1_0.id,
        t1_0.manager,
        t1_0.name 
    from
        team t1_0 
    where
        t1_0.name=?
# memberRepository.findByTeamIdAndRoleIsTrue(team.getId())
Hibernate: 
    select
        m1_0.id,
        m1_0.birthday,
        m1_0.name,
        m1_0.role,
        m1_0.team_id,
        m1_0.team_name,
        m1_0.work_start_date 
    from
        member m1_0 
    left join
        team t1_0 
            on t1_0.id=m1_0.team_id 
    where
        t1_0.id=? 
        and m1_0.role
# memberRepository.save(request.toEntity(team))
# 분명 코드 상으로는 team과 member의 매니저 수정이 먼저지만,  저장 쿼리가 먼저나갑니다.
Hibernate: 
    insert 
    into
        member
        (birthday, name, role, team_id, team_name, work_start_date) 
    values
        (?, ?, ?, ?, ?, ?)
# team.updateManager(managerName) 
# team의 manager이름의 변경을 감지하고, update쿼리를 날림
Hibernate: 
    update
        team 
    set
        manager=?,
        name=? 
    where
        id=?
# member.changeRole()
# 기존에 있던 member의 역할변경 (manager -> member)를 감지하고 update쿼리를 날리는 모습 
Hibernate: 
    update
        member 
    set
        birthday=?,
        name=?,
        role=?,
        team_id=?,
        team_name=?,
        work_start_date=? 
    where
        id=?

늦은 답 죄송합니다 🥲
나머지 답글은 저녁 먹고 열심히 달아보겠습니다 🔥

0개의 댓글