부트캠프에서 파이널 프로젝트를 진행하며 Entity를 생성하다가 문뜩 이런 생각이 들었다.
Entity의 Id값은 null이 허용되지 않는데 어째서 Wrapper class인 Long타입으로 작성할까?
기존에는 단순히 하이버네이트에서 권장하는 스펙이 Wrapper class라서 생각없이 작성했지만 저번 멘토님이 지적해주신 사항이 떠올라 찾아보기로 했다.
1. 표현 범위
만약 실제로 배포가 완료된 서비스에서 int형을 사용한다면 저장 범위를 뛰어넘는 수가 Id값에 할당될 수 있다.
2. Null 처리
원시타입 long에 null이 들어온다면 0으로 값이 처리된다. 하지만 entity의 관점에서 봤을땐 0은 null이 아닌 값으로 본다.
id가 없을때 null을 선언함으로 id가 없다는 것을 보장할 수 있게 처리한다.
한번 테스트해보자
테스트를 위한 정말정말 간단한 TestEntity
package study.datajpa.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class TestEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long testId;
private String message;
}
정말정말 간단한 테스트 코드
package study.datajpa;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import study.datajpa.domain.TestEntity;
@Transactional
@SpringBootTest
public class EntityTest {
@PersistenceContext
EntityManager em;
@Test
@Rollback(value = false)
void entityTest() {
TestEntity testEntity = new TestEntity();
// id값이 초기화되어 0이 출력된다.
System.out.println("testEntity.getTestId() = " + testEntity.getTestId());
em.persist(testEntity);
}
}
다음과 같은 코드를 작성해서 테스트를 돌려봤을때 sout에는 0이라는 숫자가 출력된다.
만약 다음과같은 상황에서 null체크를 하는 코드를 작성해서 적용시키면 해당 방식은 유효성 검사를 통과해버릴것이다.
하지만 Long타입을 사용해서 id값을 설정해주면 0이 아닌 null로 값이 출력되는것을 확인할 수 있다.
그렇다면 em.persist로 long타입 id를 가지고 있는 entity를 저장해주면 어떤일이 벌어질까?
놀랍게도 문제없이 시퀀스값이 id로 들어가있는 모습을 볼 수 있다.
Entity를 저장하는 쿼리를 발생할때 Id값에 할당되어있는 값과 무관하게
@GeneratedValue(strategy = GenerationType.SEQUENCE) 의 옵션이 적용되는 모습을 찾아볼 수 있다.
그렇다면 Entity의 Id값을 임의로 지정하고 persist하면 어떤일이 벌어질까?
package study.datajpa;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import study.datajpa.domain.TestEntity;
@Transactional
@SpringBootTest
public class EntityTest {
@PersistenceContext
EntityManager em;
@Test
@Rollback(value = false)
void entityTest() {
TestEntity testEntity = new TestEntity();
// 변경된부분 ====================================
testEntity.setTestId(5L);
// 변경된부분 ====================================
System.out.println("testEntity.getTestId() = " + testEntity.getTestId());
em.persist(testEntity);
}
}
놀랍게도
jakarta.persistence.EntityExistsException: detached entity passed to persist: study.datajpa.domain.TestEntity
중복된 엔티티를 저장할때 발동되는 EntityExisisException이 발동된다.
GeneratedValue로 시퀀스값을 설정하겠다고 선언했는데 id가 이미 세팅되어 있어 해당 익셉션이 발생하는 것 같다.
그렇다면 변경된 부분만 변경하는 merge를 사용하면 저장이 될까?
package study.datajpa;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import study.datajpa.domain.TestEntity;
@Transactional
@SpringBootTest
public class EntityTest {
@PersistenceContext
EntityManager em;
@Test
@Rollback(value = false)
void entityTest() {
TestEntity testEntity = new TestEntity();
testEntity.setTestId(52L);
System.out.println("testEntity.getTestId() = " + testEntity.getTestId());
// 변경된부분 ====================================
em.merge(testEntity);
// 변경된부분 ====================================
}
}
성공적으로 저장되는 모습을 볼 수 있다.
결론
Id 값을 Wrapper 타입으로 선언해주는 이유는 값이 없는 경우 확실히 null임을 선언해주기 위해서이다.
원시타입의 0은 시퀀스를 자동으로 할당해주지만 임의로 id값을 set해주면 EntityExistsException이 발생한다.
어렵고 어려운 JPA의 세계
'코딩딩 > Spring' 카테고리의 다른 글
JUnit5 정리 (1) | 2024.12.11 |
---|---|
th:object (0) | 2024.01.18 |
JPA 엔티티 컬렉션 성능 최적화 (0) | 2023.12.30 |
JPQL (0) | 2023.11.18 |
객체지향 쿼리 언어 (JPQL) (0) | 2023.11.13 |