자동 생성(인조키) vs 자연키
모든 엔티티는 반드시 @Id가 하나 필요하다.
나는 기존에 거의 모든 프로젝트에 이런 인조키를 사용하면서 살아왔다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
하지만?
지금 다니는 회사에서는 이러한 경우가 거의 없고 비즈니스 적으로 의미가 있는 컬럼들을 PK로 사용한다.
그렇다면 이런 경우에 JPA에서 어떻게 구현할 수 있을까?
자연키를 쓸 때는 Persistable<T> 인터페이스 구현을 권장한다고 한다.
왜냐하면 자연키만을 사용하면 문제가 발생한다.
문제점
예를 들어 다음과 같은 엔티티가 있다고 하자.
@Entity
@Table(name = "itm_i")
public class Item extends BaseEntity {
@Id
@Column(name = "item_code", length = 18)
private String itemCode; // → item_code VARCHAR(18) PK
@Column(name = "item_name", nullable = false)
private String itemName; // → item_name VARCHAR(255) NOT NULL
....
save() 를 할때 JPA는 내부적으로 어떻게 동작할까?
save(entity)
- entity.getId()가 null 이면 -> persist() (INSERT)
- entity.getId()가 null이 아니면 -> merge(SELECT -> INSERT or UPDATE)
하지만 우리는 처음부터 item_code를 부여해줄 것이다.
그렇기 때문에 새 엔티티임에도 불구하고 merge()가 호출된다.
당연히 이 값은 새로운 값일테니 불필요한 SELECT가 먼저 실행되게 되는것이다.
이를 해결하기 위해 Persistable<T> 인터페이스로 새 엔티티인지 아닌지 판별할 수 있다.
Persistable (Spring Data Core 4.0.4 API)
Simple interface for entities. Note that methods declared in this interface (getId() and isNew()) become property accessors when implementing this interface in combination with @AccessType(PROPERTY). Either of these can be marked as transient when annotate
docs.spring.io
위 문서를 봐보면 getId()와 isNew()라는 아주 심플한 두개의 메서드가 있다.
@Entity
public class Item extends BaseEntity implements Persistable<String> {
@Id
private String itemCode;
@Override
public String getId() {
return itemCode;
}
@Override
public boolean isNew() {
return getCreatedAt() == null; // BaseEntity 필드로 판단
}
}
나같경 BaseEntity에 create ID, 시간 + update id 시간.. 같은걸 가지고 있기에 cerateAt을 isNew의 기준점으로 삼았다.
정리를 해보면 다음과 같다.
| 상태 | createdAt | isNew() | save() 동작 |
| 신규 생성 직후 | null | true | persist() → INSERT 바로 실행 |
| DB에서 조회한 엔티티 | 값 있음 | false | merge() → UPDATE |
아무튼 자연키를 사용할때는 이렇게 불필요한 SELECT가 호출되는것을 방지해주자!
'프로젝트기록 > new wms' 카테고리의 다른 글
| [Spring] @Transactional : self-invocation 문제에 대해서 (1) | 2026.05.31 |
|---|---|
| [JPA] 복합 PK vs 대리키 + UniqueConstraint (0) | 2026.03.30 |