[JPA Persistable<T>] 자동 생성되는 인조키가 아닌 자연키를 사용할 때

자동 생성(인조키) 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> 인터페이스로 새 엔티티인지 아닌지 판별할 수 있다.

https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html

 

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가 호출되는것을 방지해주자!