스프링 데이터 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 쿼리가 실행된다.
그럼 id1을 식별자로 가진 레코드를 수정하면 어떻게 쿼리가 실행될까?
username을 "초원" → "효원"으로 바꾸고 실행해보았다.
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 시켜 보았다.
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에서 지연로딩(LAZY LOADING)이란 (0) | 2021.09.25 |
---|