flush() 후 다른 transaction에서 이전 transaction에서 insert 한 id값 조회 불가

이건영·2023년 6월 10일
0
post-custom-banner

예시

@Service
@RequiredArgsConstructor
public class UserService {
	private final HomeService carService;

	@Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertUser(Request request){
        User user = userRepository.save(
            User.Builder()
                .infoName(request.getInfoName())
                .build()
        );
        userRepository.flush();
        
        carService.insertCar(user, request.getCarName());
    }
}
@Service
@RequiredArgsConstructor
public class HomeService {

	@Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertCar(User user, String carName){
        String userId = user.getUserId();	//null
        
        ....
    }
}

이런 비즈니스 로직이 있다면 insertCar()함수에서 userId값은 null이다. flush()를 호출 했는데 왜 save값이 반영되지 않았을까?

이유

Spring의 @Transactional 주석은 단일 데이터 베이스의 transaction의 범위를 정의하는데 사용 된다. 이따 propagation 매개변수는 transaction이 서로 관련되는 방식을 정의하는데 사용 된다.

  • Propagation.REQUIRED: 기본 설정. 이는 기존 transaction이 있는 경우 해당 transaction의 컨텍스트에서 메서드가 실행됨을 의미한다. 기존 transaction이 없으면 새 transaction이 생성된다.
  • Propagation.REQUIRES_NEW: 메서드가 항상 새 transaction에서 실행됨을 의미한다. 기존 transaction이 있는 경우 새 transaction이 시작되기 전에 일시 중지된다.

Propagation.REQUIRES_NEW를 사용하는 경우 새 transaction이 생성되고 기존 transaction이 일시 중지된다. 이러한 변경 사항이 아직 커밋되지 않았기 때문에 이 새 transaction은 현재 실행 중인 transaction의 변경 사항을 아직 볼 수 없다. 따라서 방금 삽입한 엔터티는 새 transaction에 표시되지 않는다.

반면에 Propagation.REQUIRED를 사용하는 경우 메서드는 동일한 transaction 내에서 실행된다. 따라서 방금 삽입한 엔터티는 여전히 동일한 transaction 내에서 관리되므로 볼 수 있다.

transaction은 데이터 무결성을 보장하도록 설계되었으며 이를 수행하는 방법 중 하나는 커밋될 때까지 작업을 서로 격리하는 것이다. 이 원칙은 데이터베이스 transaction의 안정적인 처리를 보장하는 속성 집합인 ACID(Atomicity, Consistency, Isolation, Durability)에서 “I”(격리)로 알려져 있다.
따라서 새로 삽입된 엔터티를 새 transaction에 표시하려면 새 transaction을 시작하기 전에 첫 번째 transaction을 'commit'해야 한다. 그렇지 않으면 요구 사항을 충족하는 경우 Propagation.REQUIRED를 계속 사용할 수 있다.

그렇다면 flush와 commit의 차이점은?

JPA의 맥락에서 flush와 commit은 비슷하다고 착각하지만 말 그대로 비슷할 뿐 다르다.
둘 다 EntityManager가 현재 추적하고 있는 Entity instance 집합인 지속성 컨텍스트 관리와 관련이 있다.

  • flush: EntityManager.flush 작업은 transaction을 완료하지 않고 현재 지속성 컨텍스트를 기본 데이터베이스와 동기화하는 데 사용된다. 즉, 지속성 컨텍스트에서 관리되는 엔터티에 대한 모든 변경 사항(예: 삽입, 업데이트 또는 삭제)이 데이터베이스에서 실행된다. 그러나 이러한 변경 사항은 바로 commit되지 않는다. 즉, 아직 영구적이지 않으며 롤백할 수 있다. 따라서 flush를 호출하면 데이터베이스에서 SQL 문이 생성되어 실행되지만 여전히 동일한 transaction에 존재한다.

  • commit: 이 작업은 transaction에서 이루어진 모든 변경 사항을 데이터베이스에 영구적으로 적용한다. transaction이 커밋되면 롤백할 수 없다. transaction이 commit되면 지속성 컨텍스트의 모든 변경 사항이 아직 플러시되지 않은 경우 DB로 플러시된다.
    따라서 커밋에는 플러시가 포함되지만 플러시에는 커밋이 포함되지 않는다.

commit 전에 flush를 호출하려는 이유는 데이터베이스가 변경 사항에 어떻게 반응하는지(예: SQL 오류 포착) 확인하고 싶지만 아직 이러한 변경 사항을 영구적으로 만들 준비가 되지 않은 경우이다. 또한 때때로 ‘flush’를 호출하여 새로 삽입된 엔터티에 대해 데이터베이스에서 생성된 식별자(ID)를 가져오는 경우에도 사용 된다.

결론

flush를 호출해도 변경 사항이 다른 transaction에 반영되지 않는다!!.
commit 만이 다른 트랜젝션에도 반영된다.

따라서 Propagation.REQUIRES_NEW를 사용하는 경우 해당 변경 사항이 아직 커밋되지 않았기 때문에 이전 transaction에서 flush()를 하더라도 반영되지 않아 변경사항을 볼 수 없다.

profile
일단 해보자
post-custom-banner

0개의 댓글