[JPA] JPA의 Entity에 기본 생성자가 필수인 이유
@Entity에는 @NoArgsConstructor가 필수라고?
@Entity 어노테이션의 특성에 대해 알아보던 중, @NoArgsConstructor가 꼭 함께 쓰여야 한다는 내용을 접하게 되었다. 이를 계기로 그동안 습관적으로 클래스에 선언하던 어노테이션들에 대해 다시 한번 생각해보게 되었다.
이전에 개발했던 프로젝트를 들춰보니 아니나 다를까 @Entity와 @NoArgsConstructor가 함께 선언되어 있음을 발견할 수 있었다.
그리고, @NoArgsConstructor를 주석 처리하니 위와 같이 'public 혹은 protected의 기본 생성자를 가져야 한다'는 내용의 에러 메세지를 확인 할 수 있었다. 위의 에러 메세지에서 확인할 수 있듯 @Entity가 @NoArgsConstructor를 필요로 한다는 말인즉,
JPA의 Entity는 기본 생성자를 필수로 가져야 한다는 의미임을 알 수 있었다.
Java Reflection API
Entity가 기본 생성자를 필요로 하는 이유를 알기 위해서는 Java Reflection API에 대해서 먼저 알 필요가 있다.
Java Reflection API란?
구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 Java API
예를 들어 설명해 보겠다.
아래와 같은 Car 클래스가 있다.
public class Car {
private String name;
private int position;
public Car(String name, int position) {
this.name = name;
this.position = position;
}
public String move() {
position++;
}
}
일반적인 경우 아래와 같이 Object로 선언된 객체는 Car로 생성이 되었다 하더라도 컴파일 과정에서 Object 타입으로 결정이 된다.
즉, Car 클래스의 move() 메서드에는 접근이 불가능하게 된다.
public static void main(String[] args) {
Object obj = new Car("foo", 0);
}
이러한 부분을 해결해 주는 것이 Java Reflection API이다.
public static void main(String[] args) throws Exception {
Object obj = new Car("foo", 0);
Class carClass = Car.class;
Method move = carClass.getMethod("move");
// move 메서드 실행, invoke(메서드를 실행시킬 객체, 해당 메서드에 넘길 인자)
move.invoke(obj, null);
Method getPosition = carClass.getMethod("getPosition");
int position = (int)getPosition.invoke(obj, null);
System.out.println(position); // 출력 결과: 1
}
기본 생성자가 필요한 이유
다시 JPA로 돌아와서 이야기를 이어가보자.
JPA는 DB 상의 데이터를 Java 객체에 매핑할 때, 기본 생성자로 객체를 생성한 이후에 실질적인 필드 값을 주입한다.
Reflection API가 생성자의 인자 정보까지 조회할 수는 없기 때문에 일단 기본 생성자가 있어야 객체를 생성할 수 있고, 그렇게 생성된 객체를 통해 Reflection API를 활용하여 필드값을 주입할 수 있다.
생성자가 private이면 안되는 이유
앞서 기본 생성자는 public 혹은 protected이어야 한다고 언급했는데, 왜 private이면 안되는 걸까?
JPA가 매핑한 Entity를 조회할 때 hibernate가 생성한 proxy 객체를 사용하여 연관된 데이터를 실제 사용하는 시점에 조회할 수 있는데,
proxy 객체는 직접 만든 객체 클래스를 상속하기 때문에 public 혹은 protected 기본 생성자가 필요하다.
*private은 접근 범위를 해당 클래스 내로 한정한다