왜 JPA의 Entity는 기본 생성자가 반드시 필요할까?

이성민·2025년 11월 8일

woowacourse

목록 보기
6/12
post-thumbnail

이 글은 우테코 오픈미션을 진행하던 도중 예상치 못한 JPA 오류를 마주하고 그 원인과 학습 내용을 정리한 글이다.

처음엔 단순히 “코드가 잘못된 건가?” 정도로 생각했지만 결국 JPA가 내부적으로 객체를 어떻게 생성하는지를 이해하게 된 계기가 되었다.


갑자기 뜬 오류?

개발중에 갑자기 이런 오류 문구가 떴다. 분명 나는 잘 작성한거 같은데..라는 생각으로 눌러서 확인을 해봤다.


원인은 StoredBook에 public 또는 protected 기본 생성자가 있어야 한다는 뜻이였다.

그래서 내 코드를 확인해 보았다.

package smiinii.object_oriented_library.domain;

import jakarta.persistence.*;

@Entity
public class StoredBook {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Enumerated(EnumType.STRING)
    private StoredBookStatus status;

    private long bookId;

    private StoredBook(long bookId, StoredBookStatus status) {
        this.bookId = bookId;
        this.status = status;
    }

    public static StoredBook createAvailable(long bookId) {
        return new StoredBook(bookId, StoredBookStatus.AVAILABLE);
    }

    public static StoredBook createOnHold(long bookId) {
        return new StoredBook(bookId, StoredBookStatus.ON_HOLD);
    }
}

역시나 기본 생성자가 없었다. 근데 저는 기본 생성자가 없어도 다른 생성자들이 있으니깐 상관없는거 아니야?라는 생각이였다. 순수 자바로 코딩을 할 때는 기본 생성자가 없어도 작동에 문제가 없었는데 JPA를 사용하니 오류가 생긴 것이였다.


그럼 왜 JPA는 기본 생성자가 필요할까?

순수 자바

순수 자바에서는 개발자가 new로 직접 만든다.

StoredBook storedBook = new StoredBook(1L, StoredBookStatus.AVAILABLE);

프로그램이 이 코드를 직접 실행해서 객체를 생성하기 때문에 기본 생성자가 없어도 아무 문제가 없다.

JPA

하지만 JPA는 DB에서 데이터를 꺼낼 때 리플렉션(Reflection)이라는 기술로 객체를 생성한다.
즉, new StoredBook()을 우리가 호출하지 않고 JPA 내부 코드가 자동으로 객체를 만드는 것이다.

// JPA 내부 동작 개념
Class<?> clazz = StoredBook.class;
Object entity = clazz.getDeclaredConstructor().newInstance();

이렇게 호출할 때 기본 생성자가 없으면 JPA가 객체를 만들 수 없다. 그래서 내 코드에서 오류가 난 것이다.

잠깐, 리플렉션(Reflection)이란?

리플렉션은 프로그램이 자기 자신을 분석하고 조작할 수 있는 자바의 기능이다.
클래스 이름만 알고 있어도 런타임에 그 클래스의 필드나 메서드, 생성자 등에 접근할 수 있다.

예를 들어 JPA는 아래처럼 동작한다.

Class<?> clazz = Class.forName("smiinii.object_oriented_library.domain.StoredBook");
Object entity = clazz.getDeclaredConstructor().newInstance();

위 코드는 “클래스 이름 문자열”로부터 실제 객체를 만드는 과정이다.
이렇게 리플렉션을 사용하면 new 없이도 객체를 생성할 수 있지만 그 전제 조건이 바로 ‘기본 생성자가 존재해야 한다’는 점이다.

기본 생성자는 protected가 좋다?

기본 생성자를 public으로 두면 외부에서 마음대로 사용할 수 있다.
이건 도메인 규칙을 깨뜨릴 위험이 있다.
그래서 외부 코드가 함부로 new 하지 못하게 막으면서 JPA 내부에서는 리플렉션으로 접근할 수 있도록 protected StoredBook()이라는 형태를 권장한다.


수정한 코드

package smiinii.object_oriented_library.domain;

import jakarta.persistence.*;

@Entity
public class StoredBook {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Enumerated(EnumType.STRING)
    private StoredBookStatus status;

    private long bookId;

    protected StoredBook() {}

    private StoredBook(long bookId, StoredBookStatus status) {
        this.bookId = bookId;
        this.status = status;
    }

    public static StoredBook createAvailable(long bookId) {
        return new StoredBook(bookId, StoredBookStatus.AVAILABLE);
    }

    public static StoredBook createOnHold(long bookId) {
        return new StoredBook(bookId, StoredBookStatus.ON_HOLD);
    }

    public void loan() {
        if (status == StoredBookStatus.LOANED) {
            throw new IllegalStateException("이미 대출 중입니다.");
        }
        if (status == StoredBookStatus.ON_HOLD) {
            throw new IllegalStateException("다른 회원이 예약 중입니다.");
        }
        this.status = StoredBookStatus.LOANED;
    }

    public void returnBook(boolean isReservation) {
        if (status != StoredBookStatus.LOANED) {
            throw new IllegalStateException("대출 중인 도서만 반납할 수 있습니다.");
        }
        if (isReservation) {
            this.status = StoredBookStatus.ON_HOLD;
            return;
        }
        this.status = StoredBookStatus.AVAILABLE;
    }

    public StoredBookStatus getStatus() {
        return status;
    }
}

마무리

이번 오류를 단순히 “기본 생성자가 없어서 생긴 문제”로 끝내지 않고 왜 필요한지를 직접 찾아보고 이해하는 과정에서 프레임워크(JPA)가 내부에서 어떻게 동작하는지를 조금 더 깊게 볼 수 있었다.

이번 경험을 통해 JPA가 객체를 생성하고 관리하는 방식(리플렉션 기반)을 이해하게 되었고 “코드를 내가 작성하지 않아도 내부에서는 어떤 일이 일어나는가”를 한 단계 더 생각하게 되었다.

앞으로는 단순히 오류를 해결하는 것에 그치지 않고 그 원리를 직접 확인하고 정리하는 습관을 계속 유지하고 싶다.
이런 과정을 반복하다 보면 단순히 코드를 “사용하는 개발자”가 아니라 “이해하고 설명할 수 있는 개발자”로 성장할 수 있을 거라 생각한다.

참고

JPA의 Entity는 기본 생성자가 왜 반드시 필요할까?

profile
BE 개발자

0개의 댓글