JPARepository save() 할 때 발생하는 select

qpwoeiru·2024년 9월 10일
0
post-thumbnail

현재 비밀번호 변경 API의 서비스 메서드는 아래와 같다.

@Transactional
	public void editPassword(User user, EditUserPasswordRequest request) {

		if (!passwordEncoder.matches(request.currentPassword(), user.getPassword())) {
			throw new UncorrectedPasswordException("비밀번호가 틀렸습니다.");
		}

		String encodedPassword = passwordEncoder.encode(request.newPassword());
		user.editPassword(encodedPassword);

		userRepository.save(user);
	}

위 API를 실제로 실행하면 아래처럼 쿼리가 발생한다.

Hibernate: 
    select
        u1_0.id,
        u1_0.bj_nickname,
        u1_0.deleted_at,
        u1_0.email,
        u1_0.nickname,
        u1_0.password,
        u1_0.profile_image,
        u1_0.role 
    from
        user u1_0 
    where
        (
            u1_0.deleted_at IS NULL
        ) 
        and u1_0.email=?
Hibernate: 
    select
        u1_0.id,
        u1_0.bj_nickname,
        u1_0.deleted_at,
        u1_0.email,
        u1_0.nickname,
        u1_0.password,
        u1_0.profile_image,
        u1_0.role 
    from
        user u1_0 
    where
        u1_0.id=? 
        and (
            u1_0.deleted_at IS NULL
        )
Hibernate: 
    update
        user 
    set
        bj_nickname=?,
        deleted_at=?,
        email=?,
        nickname=?,
        password=?,
        profile_image=?,
        role=? 
    where
        id=?

총 2번의 select와 1번의 update가 발생한다.

첫 번째 select의 경우, 컨트롤러에서 JWT로 User 객체를 가져오는 @AuthedUser 어노테이션 때문에 발생하는 쿼리이다.

어디서 select가 한 번 더 발생하는 건지 모르겠어서 save()를 제거하고 실행해보았다. 그 결과 select, update 쿼리 모두 발생하지 않았다. 그럼 save() 할 때 select, update가 한 번씩 발생한단 뜻인데 select는 왜 발생할까?


save() 메서드는 캐시 데이터와 비교하기 위해 select를 수행한다

컨트롤러단에서 서비스 메서드로 가져온 User 객체는 준영속 상태이다. 즉, DB에서 한 번 가져왔지만 영속성 컨텍스트가 관리하지 않는 엔티티라는 뜻이다. 이러한 객체는 JPA가 관리하지 않으므로 객체를 수정해도 Dirty checking이 안되기에 DB에 update가 일어나지 않는다.

이 때 save() 메서드를 수행하면 새로운 데이터를 로드하는 것이 아닌, 준영속 상태의 엔티티를 영속성 컨텍스트에 다시 병합하는 역할을 한다. 이 과정에서 데이터베이스에 해당 User 객체가 실제로 존재하는지를 확인하기 위해 select 쿼리가 발생하는 것이다. DB에 있을 경우 엔티티에 입력된 정보로 merge가 발생한다.

이렇게 준영속 상태의 엔티티 User가 merge 되면 update 쿼리가 발생하여 변경된 내용을 반영하게 된다.

JPARepository의 save() 내용을 보면 아래와 같다.

@Transactional
@Override
public <S extends T> S save(S entity) {

	if (entityInformation.isNew(entity)) {
		em.persist(entity);
		return entity;
	} else {
		return em.merge(entity);
	}
}

DB를 조회 했을 때 파라미터의 엔티티가 새로운 객체이면 persist로 해당 엔티티를 DB에 저장하고, 이미 있던 데이터라면 merge를 수행한다.

merge는 detach 된 엔티티를 다시 영속 상태로 만들기 위해 사용한다. 파라미터로 넘어온 entity가 영속성 컨텍스트 1차 캐시에 있는지 확인 후 없으면 select 쿼리를 날려 조회한다. DB에서 조회 되면 트랜잭션 커밋 시점에 파라미터 엔티티 값과 1차 캐시의 엔티티 값 간 차이가 있으면 update 쿼리로 수정한다. DB에 해당 값이 없으면 insert 쿼리가 발생한다.

결론적으로는 JPARepository의 save() 메서드에서 merge 과정을 거치지 위해 발생한 select 쿼리가 한 번 있었으며, 이 차이를 반영하기 위한 update 쿼리가 발생하게 된 것이다.


0개의 댓글

관련 채용 정보