✅ 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
'BE > Spring & Spring Boot' 카테고리의 다른 글
[Spring Boot] 멀티 모듈 프로젝트 (0) | 2025.02.26 |
---|---|
[Spring Boot] @TestMethodOrder (0) | 2025.02.25 |
[Spring Boot] Service를 Interface + ServiceImpl 구조로 사용하는 이유 (0) | 2025.02.20 |
[Spring Boot] REST Docs + Asciidoctor (0) | 2025.02.15 |
[Spring Boot] JaCoCo (Java Code Coverage) (1) | 2025.02.10 |