본문 바로가기
👨‍🏫일문일답

primary key 필드 타입은 원시형과 래퍼 클래스 중 어느 것을 사용해야 할까?

by 캔 2023. 12. 3.

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Data
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "board_no")
    private Long id;
    
    //...
}

 

이번에 기존 프로젝트 코드 리팩터링을 진행하면서 PK(기본키) 필드 타입을 무엇으로 하는 것이 맞는 건지 생각해 보게 되었다. 원시형 타입과 래퍼 클래스, 두 가지 경우가 가능하지만, 하이버네이트 공식 문서에서 래퍼 클래스를 사용을 권장해서 래퍼 클래스를 주로 사용하는 것으로 알고 있었는데, null 가능성을 열어두는 것이 별로 좋지 않아 보였다. 하지만 이번에 구글링과 고민을 통해서 생각이 바뀌었다. PK 필드는 래퍼 클래스를 사용하는 것이 맞다.

 

We recommend that you declare consistently-named identifier attributes on persistent classes and that you use a nullable (i.e., non-primitive) type.

 

https://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#entity-pojo-identifier

 

일단, 대표적인 JPA 구현체인 hibernate의 공식 문서를 살펴보았다. nullable 타입(즉, 래퍼 클래스)을 권장한다고만 나와 있고 정확한 이유가 없었다. 처음에는 PK의 경우 null이 들어갈 수 없는데 nullable한 래퍼 클래스를 사용해야 하는 이유가 떠오르지 않았다. 오히려 NPE 가능성을 열어두는 게 마음에 들지 않았다.

 

그러다 스택 오버플로의 답변을 보게 되었다.

you can't distinguish the default value of a primitive int 0 from an assigned 0 while there is no possible ambiguity with a null(a null id always means a new entity), which is why I prefer to use a nullable wrapper type.

해석: 원시형 정수 기본값 0과 할당된 0을 구분할 수 없지만, null을 사용하면 분명해지기 때문에(null은 항상 새로운 엔티티를 의미함) nullable한 래퍼 타입을 선호한다.

https://stackoverflow.com/questions/3535791/primitive-or-wrapper-for-hibernate-primary-keys

 

지금까지 생각을 못했었는데 PK가 0이 될 수도 있었다. 매번 auto_increment가 1부터 시작하니 pk가 0인 경우를 보지 못했었다. 하지만 PK가 0인 레코드가 있을 수 있고 애플리케이션에서 실수로 값을 할당하지 않아 기본값인 0이 들어갔다면 PK가 0인 데이터를 삽입하거나 조회하게 될 수도 있다. 이 경우 데이터가 삽입되었음에도 삽입되지 않았다고 생각하거나 잘못된 데이터(PK가 0인 레코드)를 조회하게 된다. 데이터가 0이어서 생긴 문제임을 한 번에 알아차린다면 해결이 쉽지만, 그렇지 못한다면 원인을 찾기 힘들다.

 

정리해 보면, PK 필드를 래퍼 클래스를 써야 하는 이유는 숫자 원시형 타입의 경우 기본 값이 0이므로 값이 미할당되면 잘못된 값을 삽입하거나 조회하게 되어 디버깅에 어려움을 겪을 수 있기 때문이다. 래퍼 클래스 사용 시 null이 기본적으로 들어가서 NPE가 발생할 수는 있지만 , 값이 들어가지 않았을 경우 그 사실을 바로 알아차릴 수 있다. 이를 보면서 null이 항상 나쁜 것만은 아니라는 생각이 들었다.

 

제일 위 코드에서 Long이 아닌 Integer를 사용한 것은 더 큰 범위를 표현할 수 있기 때문이다. PK가 정수형 범위를 넘을 경우까지 대비할 수 있어서 사용하였고 앞으로도 int나 long, Integer보다는 기본적으로 Long 타입을 사용하려고 한다.

 

참고