개발/JPA

JPA에서 save할때 select 쿼리가 먼저 실행되는 이유

갓생사는 김초원의 개발 블로그 2021. 9. 23. 01:07

스프링 데이터 JPA의 JpaRepository로 save를 해보다가 이상한 점을 발견했다.

MEMBER 테이블에 회원 객체를 저장하는 테스트 코드를 작성했다.

@Test
@DisplayName("회원 객체 등록 테스트")
void insertMemberTest() {
    // given
    Member member = Member.builder().id("id1").username("초원").age(26).build();

    // when
    Member saveMember = memberRepository.save(member);

    // then
    assertEquals(saveMember.getUsername(), "초원");
}


쿼리 로그를 출력해보니 insert 쿼리 전에 select 쿼리가 선실행되고 있었다.

Hibernate: 
    /* load com.study.entity.Member */ select
        member0_.id as id1_0_0_,
        member0_.age as age2_0_0_,
        member0_.name as name3_0_0_ 
    from
        member member0_ 
    where
        member0_.id=?

Hibernate: 
    /* insert com.study.entity.Member
        */ insert 
        into
            member
            (age, name, id) 
        values
            (?, ?, ?)

 

JPA를 이제 막 공부하는 나는 당황스러웠다. insert 쿼리 전에 도대체 select 쿼리가 왜 실행될까?




김영한님의 자바 ORM 표준 JPA 프로그래밍에 다음과 같은 설명이 있다.

save(S) 메소드는 엔티티에 식별자 값이 없으면(null이면) 새로운 엔티티로 판단해서 EntityManager.persist를 호출하고 식별자 값이 있으면 이미 있는 엔티티로 판단해서 EntityManager.merge()를 호출한다.

위 내용에 기반하여 조금 더 자세하게 살펴보았다.

 

식별자 값인 id를 엔티티에 직접 지정하여 insert하면 위에서 설명했듯이 select 쿼리가 선호출되고 그 다음 insert 쿼리가 실행된다.

02

 

그럼 id1을 식별자로 가진 레코드를 수정하면 어떻게 쿼리가 실행될까?
username을 "초원" → "효원"으로 바꾸고 실행해보았다.

03

 

Hibernate: 
    /* load com.study.entity.Member */ select
        member0_.id as id1_0_0_,
        member0_.age as age2_0_0_,
        member0_.name as name3_0_0_ 
    from
        member member0_ 
    where
        member0_.id=?

Hibernate: 
    /* update
        com.study.entity.Member */ update
            member 
        set
            age=?,
            name=? 
        where
            id=?

 

마찬가지로 select 쿼리가 선호출되었고, 기존에 id1을 식별자로 가지는 레코드를 수정한 것이기 때문에 이번에는 update 쿼리가 호출되었다.

 

이번에는 아무 변경도 없이 한번 더 save 시켜 보았다.

04

 

Hibernate: 
    /* load com.study.entity.Member */ select
        member0_.id as id1_0_0_,
        member0_.age as age2_0_0_,
        member0_.name as name3_0_0_ 
    from
        member member0_ 
    where
        member0_.id=?

이번에는 select만 호출되었다. 변경된 필드가 없기 때문에 불필요한 update쿼리가 실행되지 않았다.

 

 

번외로 하나 더 테스트해보았다. IDENTITY 전략을 사용하여 기본키 생성을 데이터베이스에 위임하였다. 그리고 엔티티에 식별자 값을 지정하지 않은채로 save해보았다.

 

- 테이블 변경

CREATE TABLE MEMBER (
    ID VARCHAR(255) NOT NULL AUTO_INCREMENT PRIMARY KEY, --아이디(기본키)
    NAME VARCHAR(255), --이름
    AGE INTEGER NOT NULL, --나이
    PRIMARY KEY (ID)
);

 

- 엔티티 수정

@Entity
@Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "MEMBER")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 추가
    private String id;

    @Column(name = "NAME")
    private String username;

    private int age;
}

 

- 테스트 코드

@Test
@DisplayName("회원 객체 등록 테스트")
void insertMemberTest() {
    // given
    Member member = Member.builder().username("현호").age(25).build();

    // when
    Member saveMember = memberRepository.save(member);

    // then
    assertEquals(saveMember.getUsername(), "현호");
}

 

- 실행 쿼리

Hibernate: 
    /* insert com.study.entity.Member
        */ insert 
        into
            member
            (id, age, name) 
        values
            (null, ?, ?)

식별자 값을 지정하지 않으니 해당 save가 insert인지 update인지 판단할 필요가 없기 때문에 select쿼리가 선호출되지 않고 바로 insert 쿼리가 실행되었다.

🐶결론🐱

JpaRepository의 save메소드를 호출할 때 엔티티에 식별자 값이 지정되어있으면 내부적으로 select쿼리가 선 실행된다.

select 쿼리를 실행한 결과에 따라서 다음에 실행할 쿼리가 결정된다.

  • 테이블에 지정한 식별자 값이 없는 경우 - insert 쿼리 실행
  • 테이블에 지정한 식별자 값이 이미 존재하는 경우 - update 쿼리 실행
  • 테이블에 지정한 식별자 값이 이미 존재하지만 데이터의 변경이 없는 경우 - 쿼리 실행 안함

 

คʕ•ﻌ•ʔค ✿˘◡˘✿