
여러분들은 JPA를 사용하며 설정한 엔티티 객체에 @NoArgConstructor 에노테이션을 설정하지 않았을 경우 또는 이에 대한 accessLevel을 PRIVATE으로 설정했을 때, 컴파일 에러가 발생함을 경험한 적이 있을 것이다.
왜인지 이유를 몰랐으나 그냥 넘어갔던 경험이 많아 이번에는 원인을 찾아보고 이해하는 과정에서 JPA가 Reflection API를 사용하기 때문에 발생한 일임을 알게되어 정리해보고자 한다.
Reflection API는 구체적인 클래스 타입을 알지 못해도 그 클래스의 이름을 통해 클래스 정보(메서드, 타입, 변수 등등)에 접근할 수 있게 해주는 자바 API이다.
데이터에 잘못된 변동을 가져올 수 있는 setter 메서드를 필요로하지 않고 객체의 값을 초기화할 수 있으며, 기본생성자로 객체를 생성하기 때문에 Reflection을 사용하기 위해선 기본 생성자가 필수적이다.
Reflection API는 직접 접근할 수 없는 private 필드와 메서드에 접근 가능하기 때문에 내부를 노출하면서 추상화가 깨진다. 이로 인해 예기치 못한 부작용이 발생할 수 있어 웬만해서 사용을 자제하는 것이 좋다.
자바로 만든 프로그램을 실행시키면 JVM이 소스 코드를 .class 파일로 변환시킨 뒤 static 영역에 저장하는데 Reflection은 해당 static 영역에 접근이 가능하기 때문에, 이름만으로 클래스 대한 대부분의 정보에 접근이 가능한 것이다. 하지만 생성자의 매개변수에 대한 정보에는 접근이 불가능하여 기본생성자로 객체를 생성하는 것이다.
Class<?> findClass = Class.forName("Car");
Constructor<?> constructor = findClass.getConstructor(null);
Car car1 = (Car)constructor.newInstance();
Reflection API의 이해를 돕기 위해 참고자료에서 예제를 가져왔다.
클래스의 이름 만으로 클래스에 대한 정보를 가져와 인스턴스를 생성하는 코드이다.
이렇듯 Reflection API를 사용하면 이름만을 통하여 클래스에 대한 정보를 흭득할 수 있다.

스프링부트로 서버를 개발하는 우리가 자주 사용하는 JPA에서 지연로딩에 사용되는 Proxy 객체를 생성할 때 Reflection API가 사용된다.
JPA가 지연로딩(Lazy Loading) 매핑을 설정한 엔티티를 조회할 경우 hibernate는 프록시(Proxy) 객체를 생성하여 사용하는데 이를 Reflection API가 수행하기 때문이다.

프록시 객체는 실제 엔티티 클래스를 상속받은 객체로 Reflection API로 객체를 생성하기 위해 public 또는 protected인 기본 생성자가 필요하다.
그리고 실제 엔티티 또한 public 또는 protected인 기본 생성자가 존재해야 Proxy객체도 기본 생성자를 사용할 수 있기 때문에 실제 엔티티에도 기본 생성자가 필요한 것이다..!

기본 생성자가 존재한다고 해도 private 접근제어자를 설정한다면 또다시 public, protected 중 하나의 접근제어자를 사용하라며 컴파일 에러가 발생할 것이다.
상속에 대해 공부했다면 알고 있는 내용이겠지만 한 차례 짚고 넘어가자면,
부모 클래스로부터 상속받은 메소드 및 필드는 엄연히 부모 클래스에 정의된 것이고 부모의 것이다. 따라서 부모 클래스의 생성자가 호출되어야 자식 클래스에서 사용이 가능한 것이다.
부모 클래스의 생성자는 자식 클래스의 생성자로 인스턴스를 생성할 때 자동으로 호출된다.
지금까지 말한 내용들을 정리하자면 Reflection API는 기본 생성자로 객체를 생성하며,
JPA는 Reflection API를 사용하여 프록시 객체를 생성하기 때문에 프록시 객체가 상속받는 엔티티 객체에 기본 생성자가 필요하다.
이는 프록시 객체는 엔티티 객체를 상속받기 때문에 엔티티 객체의 기본 생성자의 접근 제어자를 private으로 설정해선 안된다는 것을 뜻한다.
reference
좋은 글 잘 보고 갑니다!