갓생사는 김초원의 개발 블로그
chocho_log
갓생사는 김초원의 개발 블로그
전체 방문자
오늘
어제
  • 분류 전체보기 (77)
    • 개발 (22)
      • Spring (4)
      • Java (3)
      • Database (2)
      • Elasticsearch (3)
      • ETC (3)
      • JPA (3)
      • 이슈 (1)
    • 코딩 테스트 (43)
      • 프로그래머스 (23)
      • 백준 (12)
      • TIP (8)
    • 자료구조 (2)
    • 알고리즘 (4)
    • 잡생각 (0)
    • 경험 (5)
      • AWS re:Invent 2024 (5)

블로그 메뉴

    공지사항

    인기 글

    태그

    • Lazy Loading
    • jar
    • war
    • 디자인패턴 #SOLID 원칙
    • querydsl
    • 지연로딩
    • jpa
    • Spring Boot Embedded Tomcat

    최근 댓글

    최근 글

    갓생사는 김초원의 개발 블로그

    chocho_log

    개발/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 쿼리 실행
    • 테이블에 지정한 식별자 값이 이미 존재하지만 데이터의 변경이 없는 경우 - 쿼리 실행 안함

     

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

    '개발 > JPA' 카테고리의 다른 글

    JPA의 delete 메서드 비교(delete, deleteAll, deleteAllInBatch)  (0) 2025.02.21
    JPA에서 지연로딩(LAZY LOADING)이란  (0) 2021.09.25
      '개발/JPA' 카테고리의 다른 글
      • JPA의 delete 메서드 비교(delete, deleteAll, deleteAllInBatch)
      • JPA에서 지연로딩(LAZY LOADING)이란
      갓생사는 김초원의 개발 블로그
      갓생사는 김초원의 개발 블로그
      갓생사는 김초원의 개발 블로그 github: https://github.com/kimchowon

      티스토리툴바