[Spring] Reflection API 과 JPA 엔티티에 기본 생성자가 필요한 이유

최동근·2023년 2월 15일
0

스프링

목록 보기
5/8

안녕하세요 오늘은 Reflection APIJPA 엔티티 에 파라미터가 없는 기본 생성자가 필요한 이유에 대해 알아보겠습니다 👨‍💻

🌈 Reflection API 에 대해

Spring 을 학습하다 보면 Java Reflection API 를 자주 접하게 됩니다.
해당 API 는 사실 자바 관련 개념에서는 자주 쓰이지 않으며 보통 프레임워크나 라이브러리에 자주 사용됩니다 👨‍💻

Reflection API 란 구체적인 클래스 타입을 알지 못해도 클래스의 정보(메서드, 타입, 변수 등) 에 접근할 수 있게 해주는 API 입니다.

이 문장은 Reflection API 에 대해 가장 잘 나타낸 문장이지만 처음 해당 개념을 접한 사람이라면 이해하기 어렵습니다.
예시를 통해 깊게 알아보겠습니다 💪

예제

public class Child {
	
    private String name;
    private Integer age;
    private String gender;
    
    public Child(String name, Integer age, String gender) {
    	
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    
    public void getOld() {
    	this.age ++;
    }
    
    public void changeName(String newName) {
    	this.name = newName;
    }
    
    public void changeGender() {
    	if(this.gender.equals("M") {
        	this.gender = "Fe";
        }else {
        	this.gender = "M";
        }
   }

자바의 특징 중 하나인 다형성 덕분에 Child 클래스의 부모 타입 클래스로 Child 클래스를 참조할 수 있습니다.

public static void main(String[] args) {
	Object obj = new Child("Choi",27,"M"); // Object 타입은 모든 클래스의 부모 타입
    obj.changeName("Park"); // 이름 변경 메소드 호출
}

위에 코드 처럼 obj 타입의 참조 변수로 자식 타입의 메소드인 changeName 을 호출하면 어떻게 될까요?
결과는 컴파일 에러가 납니다 ⛔️

  public static void main(String[] args) {
  	Object obj = new Child("Choi",27,"M"); // Object 타입은 모든 클래스의 부모 타입
      obj.changeName("Park"); // 컴파일 에러 발생 java : cannot find symbol ⛔️
 }

이렇게 컴파일 에러가 나는 이유는 obj 참조 변수는 Object 클래스라는 타입만 알 뿐, 자식 클래스의 구체적인 타입을 알지 못합니다.
그렇기에 해당 클래스의 정보(변수, 타입, 메소드 등) 에 접근하지 못합니다 🙅
하지만 이를 가능하게 해주는 것이 Reflection API 입니다 🧙‍♀️

public static void main(String[] args) throw Exception{
	Object obj = new Child("Choi", 27, "M");
    Class childClass = Child.class;
    Method changeName = childCalss.getMethod("changeName");
    
    changeName.invoke(obj, "ParK");
    
    ...
}

해당 코드는 Reflection API 를 통해 구체적인 클래스 Child 을 알지 못해도 changeName 메소드에 접근한 것입니다.

이렇게 마법같은 일이 가능한 이유는 무엇일까요?
Java 에서는 JVM 이 실행되면 사용자가 작성한 자바 코드가 컴파일러를 거쳐 바이트 코드로 변환됩니다.
이때 변환된 바이트 코드는 static 영역에 저장되는데, Reflection API 는 이 정보를 활용합니다.
이때 클래스의 이름만 알고 있다면 static 영역을 탐색하여 해당 클래스이 정보를 가져올 수 있습니다 ✌️

🌈 JPA 엔티티에 기본 생성자가 필요한 이유

JPA 엔티티를 생성할 때 엔티티 클래스에 기본 생성자는 필수로 들어가야합니다.
왜그럴까요? 이것은 앞에서 다루었던 Reflection API 와 관련이 있습니다 👨‍💻

JPA 는 엔티티 조회시 데이터베이스 값을 객체 필드에 주입해야 합니다.
이러한 과정은 컴파일 시점이 아니라 어플리케이션이 돌아가고 있는 런타임 시점에 이루어집니다.
이때 Reflection API 을 이용하여 데이터베이스 값(칼럼 값) 을 엔티티에 매핑합니다 ❗️

Refelction API 를 사용하기 위해서는 기본 생성자가 필수적입니다.

이러한 이유 때문에 Reflection API 동작으로 생성되는 JPA 엔티티 클래스에는 기본 생성자가 필요합니다.
여기서 주목해야할 점은 기본 생성자의 접근 제어 지시자는 Protected 혹은 Public 이어야 합니다 👀
즉 Private 로 선언하면 동일한 에러를 맞이하게 됩니다 🙅

🌈 JPA 엔티티에 기본 생성자가 Private 이면 안되는 이유

앞에서 다루었던 것처럼 JPA 엔티티는 Reflection API 로 인해 생성되는데 Protected 와 Public 으로 기본 생성자를 선언해야합니다.
이는 Proxy 객체 와 관련이 있습니다 🤔

Proxy 객체 란 엔티티 조회시 지연 로딩 을 사용할 때 만들어집니다.
조회 성능 향상을 위해 지연 로딩 이 선언된 엔티티는 실제로 해당 엔티티가 사용될때 조회 쿼리를 날리게 되는데 이를 지연 로딩 이라고 합니다.

이때 지연 로딩 에 결과로 원본 엔티티가 아니라 원본 엔티티를 상속한 Proxy 객체 가 반환되고
실제로 해당 엔티티가 조회 시 원본 엔티티를 Proxy 객체 가 참조하여 Proxy 객체 를 통해 원본 엔티티에 접근할 수 있게됩니다.
이때 Proxy 객체 가 원본 엔티티를 상속하기 때문에 Protected 혹은 Public 기본 생성자가 필요합니다 😉

만약 JPA 엔티티의 기본 생성자를 Private 으로 설계한다면 JpaSystemException 오류를 맞이하게 됩니다 🙅


참조

Reflection API 간단히 알아보자.
JPA Entity 생성자에 관하여
Java의 Reflection과 JPA 엔티티의 기본 생성자 protected를 권장하는 이유

profile
비즈니스가치를추구하는개발자

0개의 댓글