본문 바로가기
JPA

JPA는 왜 기본생성자가 필요할까? (feat. 접근 제어자 범위)

by CodingMasterLSW 2025. 5. 26.

 

This default constructor is used by JPA to create instances of the entity class using Reflection. It provides a way to dynamically create instances of classes, call methods, and access fields.

 

https://www.baeldung.com/jpa-no-argument-constructor-entity-class

 

Need for Default Constructor in JPA Entities | Baeldung

Learn about the JPA requirement for a default no-argument constructor in an Entity class.

www.baeldung.com

 

문서에 따르면 Entity 객체를 생성할 때 Reflection을 사용한다고 한다.

 

JPA는 데이터베이스에서 데이터를 읽어와 엔티티 객체(Entity)로 매핑할 때 객체를 생성한다.

EntityManager em = ...;
Member member = em.find(Member.class, 1L);

 

예시 코드다.

em.find()를 하면 member 객체가 생성이 된다. new Member()를 호출한 적이 없지만 JPA 내부에서 자동으로 객체를 생성한다.

 

어떻게 자동으로 객체를 생성할까? 

 

JPA의 구현체인 Hiberate는 위와 같이 객체를 생성한다.

Constructor<Member> constructor = Member.class.getDeclaredConstructor();
constructor.setAccessible(true); 
Member member = constructor.newInstance();

 

간단하게 정리를 하자면,

JPA는 reflection을 통해 Entity 객체를 생성하는데, reflection으로 객체를 생성하는 과정에서 기본생성자가 필요하다. 

라고 정리할 수 있다.


추가로 궁금한 점  : 왜 JPA에서는 기본생성자의 접근제어자를 public/ protected로 요구할까?

 

https://openjpa.apache.org/builds/1.2.3/apache-openjpa/docs/jpa_overview_pc.html

The JPA specification requires that all persistent classes have a no-arg constructor. This constructor may be public or protected. Because the compiler automatically creates a default no-arg constructor when no other constructor is defined, only classes that define constructors must also include a no-arg constructor.

 

스펙 명세에 따르면 JPA는 public / protected의 기본생성자를 요구하는 걸 확인할 수 있다.

 

왜 그럴까? reflection은 생성자가 private 이어도 상관없을 텐데...

 

여러 가지 이유가 있겠지만,  그중 하나는 proxy 때문이다.

Lazy 로딩을 할 경우 프록시 객체가 필요하다. 그리고 프록시 객체는 기존의 Entity를 상속받아 사용한다. 그런데 기존 Entity의 기본생성자가 private이라면 상속을 할 수가 없다. 위와 같은 이유로 private 생성자를 사용할 경우, proxy 객체 초기화를 하지 못하고,  LazyInitializerException이 발생한다.

 

간단한 테스트를 통해 실험해 보자.

 

@Entity
public class Member {

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

    private String name;

    private Member() {}

    public Member(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

 

@Entity
@Table(name = "orders")
public class Order {

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

    @ManyToOne(fetch = FetchType.LAZY)
    private Member member;

    private Order() {}

    public Order(Long id, Member member) {
        this.id = id;
        this.member = member;
    }

    public Member getMember() {
        return member;
    }
}

 

Member와 Order Entity를 만들고, 연관관계를 ManyToOne으로 해줬다.

 

눈여겨볼 부분은, 기본생성자의 접근제어자를 private으로 선언했다.

 

테스트 코드

@DataJpaTest
@ContextConfiguration(classes = DemoApplication.class)
public class ConstructorTest {

    @PersistenceContext
    private EntityManager em;

    @Autowired
    private MemberRepository memberRepository;

    @Autowired
    private OrderRepository orderRepository;

    @Test
    void a() {
        Member member1 = new Member(1L, "jenson");
        Order order1 = new Order(1L, member1);
        memberRepository.save(member1);
        orderRepository.save(order1);
        em.clear(); // 캐시 삭제
        Optional<Order> findOrderResult = orderRepository.findById(1L);
        Order order = findOrderResult.get();
        order.getMember().getName(); // 에러 발생!
    }
}

 

fetch 타입을 Lazy로 설정할 경우, 다음과 같은 오류가 발생한다.

org.hibernate.HibernateException: HHH000143: 
Bytecode enhancement failed because no public, 
protected or package-private default constructor was found for entity

 

위에서 설명한 오류상황이다. 기본생성자를 private으로 선언하니 proxy 객체가 초기화되지 않는다는 오류가 발생한다.

 

그렇다면, fetch 타입을 EAGER로 변경하면 어떻게 될까?

@ManyToOne(fetch = FetchType.EAGER)
private Member member;

 

 

성공한다...!

 

위의 실험을 통해 JPA에서 기본생성자의 접근제어자를 protected/public으로 제한하는 이유는, proxy 객체를 초기화하기 위해서라는 걸 알 수 있다. 

 

 

https://openjpa.apache.org/builds/1.2.3/apache-openjpa/docs/jpa_overview_pc.html

https://www.baeldung.com/jpa-no-argument-constructor-entity-class

참고자료

'JPA' 카테고리의 다른 글

@Transactional이 없을 때 Lazy 로딩이 실패하는 이유  (2) 2025.05.21