BE/Spring & Spring Boot

[Spring Boot, JPA] 리플렉션

baek-dev 2025. 2. 23. 18:34

JPA는 객체를 데이터베이스와 매핑할 때 “리플렉션(Reflection)“이라는 기능을 사용합니다.

✅ 리플렉션을 사용하면 클래스 내부의 정보(필드, 메서드, 생성자 등)를 실행 중(runtime)에 확인하고 변경할 수 있습니다.

JPA는 리플렉션을 통해 엔티티 객체를 생성하고 필드 값을 직접 주입합니다.

 

1. 리플렉션(Reflection) 기본 개념

🔹 (1) 일반적인 객체 생성 방식

 

자바에서 객체를 생성할 때 new 연산자를 사용합니다.

public class Member {
    private String name;

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

public class Main {
    public static void main(String[] args) {
        Member member = new Member("홍길동"); // ✅ new 연산자로 객체 생성
    }
}

new Member("홍길동")을 호출하면 개발자가 직접 생성자를 호출하여 객체를 만듦.

이 방식에서는 반드시 생성자를 호출해야만 객체를 만들 수 있음.

 

🔹 (2) 리플렉션(Reflection) 방식

 

리플렉션을 사용하면 “new 연산자를 사용하지 않고도” 객체를 생성할 수 있습니다.

즉, 생성자가 없어도 객체를 만들고, 필드 값을 강제로 설정할 수 있음.

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        // 1. 클래스 정보를 가져옴
        Class<?> clazz = Class.forName("Member");

        // 2. 리플렉션을 이용하여 객체 생성 (생성자 호출 없이)
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true); // ✅ private 생성자도 접근 가능
        Member member = (Member) constructor.newInstance();

        // 3. 필드 값 강제 설정
        var nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true); // ✅ private 필드 접근 가능
        nameField.set(member, "홍길동");

        System.out.println("이름: " + member.getName()); // ✅ 홍길동
    }
}

리플렉션을 사용하면, 클래스의 생성자를 직접 호출하지 않아도 객체를 만들 수 있음!

private 생성자, private 필드에도 접근하여 값을 변경할 수 있음.

 

2. JPA에서 리플렉션이 사용되는 이유

JPA는 엔티티 객체를 생성할 때 개발자가 직접 new를 호출하는 것이 아니라, 프레임워크 내부에서 자동으로 객체를 생성해야 합니다.

이를 위해 리플렉션을 사용하여 기본 생성자를 호출하고, 필드 값을 직접 주입합니다.

 

📌 JPA가 엔티티를 생성하는 과정

1. 데이터베이스에서 조회한 결과를 가져옴

2. 리플렉션을 사용하여 기본 생성자를 호출

3. 각 필드 값을 직접 주입

4. 완성된 엔티티 객체를 반환

 

🔹 (1) 기본 생성자가 필요한 이유

 

JPA는 리플렉션을 이용하여 기본 생성자를 호출해야 객체를 만들 수 있습니다.

따라서, JPA 엔티티에는 기본 생성자(@NoArgsConstructor)가 필수입니다.

@Entity
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    protected Member() {} // ✅ JPA가 리플렉션으로 객체를 생성할 때 사용

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

JPA는 new Member() 대신 리플렉션을 사용하여 protected Member()를 호출함.

이후, 리플렉션을 사용하여 name 필드에 값을 강제로 주입!

 

🔹 (2) JPA에서 리플렉션을 사용한 객체 생성 과정

 

JPA에서 엔티티 객체를 조회하면, Hibernate 내부적으로 다음과 같이 동작합니다.

// JPA 내부 동작 (가상 코드)
Class<?> clazz = Class.forName("Member"); // 엔티티 클래스 로딩
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); // ✅ private/protected 생성자도 접근 가능
Object entity = constructor.newInstance(); // ✅ 리플렉션을 이용한 객체 생성

즉, JPA가 new Member()를 호출하는 것이 아니라, 리플렉션을 통해 기본 생성자를 강제로 호출하여 객체를 만듦.

 

3. JPA에서 final 필드를 사용할 수 없는 이유

JPA는 리플렉션을 사용하여 필드 값을 강제로 주입하는데, final 필드는 값을 변경할 수 없기 때문에 오류가 발생할 수 있음.

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private final Long id; // ❌ ERROR 발생

    private final String name; // ❌ ERROR 발생

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

JPA는 리플렉션을 사용하여 id, name 값을 강제로 주입하려고 하지만, final 필드는 변경할 수 없기 때문에 오류 발생.

따라서 일반적으로 JPA 엔티티에서는 final 필드를 사용하지 않음.

 

🔹 (해결 방법) @NoArgsConstructor(force = true)를 사용하면 final 필드도 가능

@Entity
@NoArgsConstructor(force = true) // ✅ final 필드를 강제로 null로 초기화
@AllArgsConstructor
@Getter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private final Long id;

    private final String name;
}

이제 JPA가 final 필드를 null로 초기화할 수 있으므로 오류 없이 동작 가능!

✅ 하지만, 이 방법도 일반적이지 않으며, 대부분의 경우 final 필드를 사용하지 않는 것이 좋음.

 

4. 결론

JPA에서 리플렉션이란? ✅ JPA가 객체를 생성할 때, 기본 생성자를 강제로 호출하고 필드 값을 주입하는 기술
JPA에서 기본 생성자가 필요한 이유? ✅ 리플렉션을 통해 객체를 만들기 위해 필요
JPA에서 final 필드를 사용할 수 없는 이유? ❌ 리플렉션을 통해 값을 주입할 수 없기 때문
final 필드를 사용하고 싶다면? @NoArgsConstructor(force = true)를 사용하면 가능

 

💡 👉 “JPA는 객체를 생성할 때 직접 new를 호출하는 것이 아니라 리플렉션을 사용하여 필드 값을 주입하기 때문에, 기본 생성자가 필요하고 final 필드는 일반적으로 사용하지 않는다!” 🚀

 

 

 

 

출처 : ChatGPT