No default constructor for entity (Entity가 기본생성자가 필요한 이유)

byeol·2023년 4월 18일
0
Caused by: org.hibernate.InstantiationException:
No default constructor for entity:  : com.cos.security1.model.User

위와 같은 오류가 발생했습니다.
그 이유를 찾아보니 JPA의 엔티티는 기본 생성자가 있어야 한다고 합니다.

왜 있어야할까에 대한 의문이 생겼고
찾아보니 저와 같은 의문을 가지는 사람이 많아 이번에 정리해보려고 합니다.


기본 생성자란?

매개 변수를 하나도 가지지 않고, 아무런 명령어도 포함되어 있지 않습니다.
자바 컴파일러는 컴파일 시 클래스에 생성자가 하나도 정의되어 있지 않으면 자동으로 다음과 같은 생성자를 추가합니다.

class Car{
    private String modelName = "소나타";
    private int modelYear = 2016;
    private String color = "파란색";
 
    public String getModel(){
        return this.modelYear + "년식 " + this.color + " " + this.modelName;
    }
}
 
public class Main{
    public static void main(String[] args) {
        Car myCar = new Car();                    // 기본 생성자 호출
        System.out.println(myCar.getModel());    // 2016식 파란색 소나타
    }
}

출처 : https://miyakita.tistory.com/202

Java Reflection

리플렉션에 대한 여러가지 설명

  • 리플렉션이란
    객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법입니다. (투영, 반사)

  • 리플렉션은 힙 영역에 로드된 Class 타입의 객체를 통해, 원하는 클래스의 인스턴스를 생성할 수 있도록 지원하고, 인스턴스의 필드와 메소드를 접근 제어자와 상관 없이 사용할 수 있도록 지원하는 API입니다.

  • 컴파일된 바이트 코드를 통해 Runtime에 동적으로 특정 Class의 정보를 추출할 수 있는 프로그램 기법입니다.

    Class class = Class.forName("클래스 이름")

스프링을 공부하다가 보면 BeanFactory라는 Spring Container 개념을 학습하게 됩니다.
이 BeanFactory는 어플리케이션이 실행한 후 객체가 호출될 당시 객체의 인스턴스를 생성하게 되는데 그 때 필요한 기술이 Reflection 입니다.

자바는 스크립트 언어가 아닌 컴파일 언어입니다. 원래 자바에선는 동적으로 객체를 생성하는 기술이 없었지만 동적으로 인스턴스를 생성하는 Reflection이 그 역할을 대신합니다.

그렇다면 동적과 정적의 차이는 무엇일까요?

먼저 바인딩의 개념을 알아봅시다.
바인딩이란 프로그램에 사용된 구성 요소의 실제 값 또는 프로퍼티를 결정짓는 행위
= 프로그램에서 사용되는 변수나 메서드 등 모든 것들이 결정되도록 연결해주는 것

이 바인딩을 결정 짓는 시기에 따라 정적 바인딩과 동적 바인딩으로 나뉘게 됩니다.

정적 바인딩동적 바인딩
- 컴파일 시간에 결정- 실행 시간(Runtime)에 결정
- 프로그램이 실행돼도 변하지 않는다.- 늦은 바인딩이라고도 부른다.
- 오버로딩- 오버라이딩
- private, final, static이 붙은 메서드- Java에서의 다형성, 상송이 가능한 이유
  • 컴파일 : 소스코드를 전처리하고 컴파일러에 의해서 기계가 이해할 수 있는 저수준 언어로 작성된 목적 파일로 변경한 후, 목적 파일등을 링커가 하나의 실행 파일로 만들어 주게 됩니다.
  • 런타임 : 프로그램이 실행되고 있을 때를 런타림이라고 합니다. 0 나누기 오류, Null 참조 오류, 메모리 부족 오류 문제를 알 수 있습니다. 힙 영역의 전체 크기가 결정됩니다.

언제 사용되는가?

앞서 말했지만 Reflection은 동적 바인딩을 가능하게 합니다.
따라서 아래와 같은 경우에 활용될 수 있습니다.

  • 동적으로 Class를 사용 해야할 경우
    코드 작성 시점에서 어떠한 Class를 사용해야할지 모르지만 Runtime에 Class를 가져와서 실행해야하는 경우
     @PostMapping(value = "/kakao")
      public ResponseEntity<AuthResponse> kakaoAuthRequest(@RequestBody AuthRequest authRequest) {
          return ApiResponse.success(kakaoAuthService.login(authRequest));
      }
    제가 프로젝트에서 사용하는 코드를 가져와 보았습니다.
    저기에 있는 @RequestBody 바인딩도 JavaReflection을 사용합니다.
  • Test Code 작성
    private 변수를 변경하고 싶거나 private method를 테스트할 경우
  • 자동 Mapping 기능 구현
    IDE 사용할 때 몇 개만 입력해도 비슷한 단어가 등장하는 여러개의 Class 혹은 Method 목록들을 제시해줍니다.

결론

앞서 저는 @RequestBody에 대한 이야기를 하였습니다.
@RequestBody로 들어오는 웹 요청을 그대로 객체로 바인딩하기 위해서는
바인딩될 타입에 기본 생성자가 존재해야 합니다.

@NoArgsConstructor
public class AuthRequest {

   private String accessToken;
}
  • @NoArgsConstructor : 기본 생성자를 만들어주는 어노테이션

    만약에 기본 생성자가 존재하지 않는다면 예외가 발생합니다. 이는 스프링의 @RequestBody 바인딩 방식이 기본 생성자를 통해 객체를 생성한 후 Java Reflection을 이용해서 필드 값을 집어넣어주는 방식이기 때문입니다.
    Reflection은 클래스 이름만 알면 생성자, 필드, 메서드 등 클래스의 모든 정보에 접근이 가능합니다.

    JPA 역시 데이터를 DB에서 조회해 온 뒤 객체를 생성할 때 Reflection을 사용합니다. 그래서 기본 생성자로 객체를 생성해야 합니다.
    Reflection을 사용하는 이유는 우리가 엔티티로 어떤 타입을 생성할 지 JPA는 알수 없고 Reflection을 사용하지 않고 객체를 생성하려면 미리 객체의 타입을 알고 있어야 합니다.
    하지만 프레임워크나 라이브러리는 사용자가 정의한 구체 클래스 정보를 알 수 없습니다. 어떤 타입으로 엔티티를 만들더라도 해당 엔티티를 생성하기 위해서는 Reflection을 사용해서 엔티티 인스턴스를 만들어 주는 것입니다.

    출처 : https://prolog.techcourse.co.kr/studylogs/2489

profile
꾸준하게 Ready, Set, Go!

0개의 댓글