본문 바로가기

Spring&IntelliJ

protected기본생성자를 사용하면 왜 개발자는 함부로 생성하지 못하고 JPA같은 프레임워크는 접근이 가능한거야?

김영한 ⇒“기본적으로 JPA를 사용한다면 리플렉션이나 프록시 같은 기술을 사용해야 하는데 기본생성자가 없으면 리플랙션 혹은 프록시같은 기술을 사용할 수 없습니다. 기본생성자를 public 으로 설정하면 실수가 발생할수 있기 때문에 protected까지 허용해 줍니다.“

좋은 질문입니다. 이 부분은 Java의 접근 제어자(access modifier)JPA의 리플렉션(reflection)을 이해해야 합니다.

결론부터 말하면:

protected 기본 생성자는 일반 개발 코드에서는 접근을 제한하지만, JPA는 Java Reflection을 사용하기 때문에 객체 생성이 가능하다.

입니다.


1. protected의 기본 의미

Java 접근 제어자는 다음과 같은 순서로 접근 범위를 가집니다.

접근 제어자같은 클래스같은 패키지자식 클래스다른 패키지
public O O O O
protected O O O
default O O X X
private O X X X

protected는:

  • 같은 패키지에서는 접근 가능
  • 상속 관계에서는 접근 가능
  • 완전히 외부에서는 접근 불가능

입니다.


예를 들어:

 
package jpabook.jpashop.domain;

public class Address {

    protected Address() {
    }

}
 

다른 패키지:

 
package jpabook.jpashop.controller;

public class Test {

    public void test() {
        Address address = new Address(); // ❌ 불가능
    }
}
 

컴파일 오류가 발생합니다.

왜냐하면 Test는:

  • 같은 패키지도 아니고
  • Address를 상속하지도 않았기 때문입니다.

2. 그런데 JPA는 어떻게 생성할까?

여기서 Reflection이 등장합니다.

일반적인 객체 생성:

 
Address address = new Address();
 

이 경우 Java 컴파일러가 접근 권한을 검사합니다.

즉:

개발자 코드
    |
    |
new Address()
    |
Java 접근 검사
    |
protected라서 안됨
 

입니다.


하지만 Reflection은 다릅니다.

예:

 
Constructor constructor =
        Address.class.getDeclaredConstructor();

constructor.setAccessible(true);

Address address =
        (Address) constructor.newInstance();
 

여기서:

 
constructor.setAccessible(true);
 

가 핵심입니다.

이 코드는:

"접근 제한자를 무시하고 이 생성자를 사용하겠다"

라는 의미입니다.

그래서:

 
private Address()
 

도 생성 가능합니다.


3. JPA가 실제로 하는 일

DB에서 데이터를 조회했다고 해봅시다.

테이블:

MEMBER

ID | CITY
---------
1  | Seoul
 

JPA는 내부적으로:

DB 데이터
    |
    ↓
JPA
    |
    ↓
Reflection으로 객체 생성
    |
    ↓
Address 객체 생성
 

을 합니다.

즉 개발자가:

 
new Address()
 

를 호출하는 것이 아닙니다.

JPA 내부 코드가 Reflection으로 생성합니다.


4. 그렇다면 private 기본 생성자를 쓰면 안 되나?

기술적으로는 일부 Reflection에서는 가능합니다.

예:

 
private Address() {

}
 

하지만 JPA 스펙에서는:

엔티티와 임베디드 타입은 public 또는 protected 기본 생성자를 가져야 한다

라고 정의합니다.

그래서 권장 방식은:

 
@Embeddable
public class Address {

    private String city;

    protected Address() {
    }

}
 

입니다.


5. 왜 public보다 protected가 좋은가?

비교해 보면:

public

 
public Address() {
}
 

누구나 가능:

 
Address address = new Address();
 

문제:

 
Address address = new Address();

System.out.println(address.getCity());
// null
 

초기화되지 않은 객체가 만들어질 수 있습니다.


protected

 
protected Address() {
}
 

개발자:

 
new Address();
 

불가능

JPA:

Reflection
    |
    ↓
protected 생성자 호출 가능
 

가능

따라서:

JPA에게는 객체 생성 권한을 주고, 개발자가 잘못된 객체를 만드는 것은 막는다.

라는 장점이 있습니다.


6. 그래서 JPA 엔티티는 보통 이렇게 작성합니다.

 
@Entity
@Getter
public class Member {

    @Id
    private Long id;

    private String name;


    protected Member() {
    }


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

사용자는:

 
Member member = new Member("Kim");
 

만 가능하고,

 
Member member = new Member();
 

는 막습니다.

하지만 JPA는:

Reflection
        ↓
protected Member()
        ↓
객체 생성
 

가능합니다.


정리하면:

protected 기본 생성자 = "JPA 같은 프레임워크는 접근 가능하지만, 일반 개발자가 실수로 빈 객체를 생성하는 것은 막는 장치"

라고 이해하면 됩니다.