객체 직렬화와 항상 기본키를 참조해야 하는 이유를 실제 프로젝트를 진행하면서, 몸소 깨닫게 되었다.
위 코드와 같이, 회원(member) 엔티티와 일정(event) 엔티티가 존재한다.
이때 회원 엔티티에는 id 라는 long 타입의 기본키와, memberId 라는 string 타입의 일반 필드가 존재한다.
일정 엔티티는 회원 엔티티와 @Manytoone, 즉 다 대 일의 관계를 맺고 있으며, 일정 엔티티는 회원 엔티티의 기본키인 id가 아니라, String 타입의 일반 필드인 memberId를 참조하고 있다.
이때, memberId를 통해서 일정을 조회하려고 하니까, ClassCastException 이라는 오류가 계속해서 발생했다.
해결 법은 찾아보니, 회원 엔티티 클래스에 아래와 같은 코드,
해당 코드는 회원 엔티티를 직렬화를 해주는 코드인데, 이를 달아주니까 해결이 되었다.
implements Serializable {
private static final long serialVersionUID = 1L;
직렬화란 자바에서 객체를 데이터, 즉 바이트 형태로 변환하는 것을 의미하고, 역직렬화란 이렇게 바이트 형태로 변환된 데이터를 다시 자바 객체로 되돌리는 것을 의미한다.
근데 나는 해당 의미를 가진 직렬화라는 작업을 수행했다고 해서 오류가 사라진 것이 이해가 되지 않아, 더욱 탐색을 해보았다.
그래서 찾은 결과를 간략하게 정리하면 아래와 같다.
더욱 풀어서 설명하자면,
JPA는 기본적으로 기본키를 통해 엔티티를 관리한다.
그런데 기본키가 아닌 다른 유니크 속성을 가진 필드를 참조하는 경우에서는, 나의 경우에는 회원 엔티티의 id 필드가 아니라 unique 속성을 가졌을 뿐인 String 타입의 memberId 필드를 참조했었는데,
하여튼 해당 필드를 통해서 기본키를 찾는 과정을 먼저 수행하고,
즉, memberId 필드를 통해서 id 필드를 찾는 과정을 먼저 수행하고,
이 탐색된 기본키, id를 통해서 엔티티 객체, 즉 member 엔티티를 또 다시 찾게 된다.
그러니까 탐색과정이 한번에가 아니라 총 두번에 걸쳐서 수행되는 것이다.
이때 JPA를 구현한 하이버네이트의 입장에서는 다른 수행할 작업들도 많기 때문에, 당연하지만 처리해야할 요청이 한 두개가 아닐것 아닌가?
그래서 우리가 컴퓨터로 글을 작성할 때 임시저장을 하는 것처럼,
하이버네이트도 객체를 임시저장한다는 느낌으로 직렬화,
그러니까 바이트 단위의 데이터로 변환하여 메모리에 올려두는 작업을 수행한다고 대부분의 사람들이 추측을 한다.
이걸 확실히 이렇다가 아니라, "추측을 한다" 라는 이유가 스프링 부트의 내부 코드를 뜯어보지 않고서는 정확하게는 알 수가 없기 때문이다.
여하튼 이러한 이유 때문에 회원 엔티티를 직렬화를 해야하는데, 할 수가 없어 해당 오류가 발생했던 것이다.
사실 JPA 표준 스펙에는 모든 엔티티 클래스에 직렬화를 구현해야 한다고 나와있긴 하다.
그런데 대부분의 경우에서, 기본키가 아닌 다른 필드를 외래키로 참조하는 내가 맞닥뜨린 경우를 제외하고는, 직렬화를 하지 않았다고 해서 오류가 발생하는 경우는 거의 없다고 한다.
즉 이와 같은 문제 상황이 발생하기 때문에,
보통 일반적으로 객체를 참조할때는 꼭 기본키를 참조하라고 하는 것이라고 생각한다.
결론은 결국,