참치코더의 꿈 메모장

JPA / @MappedSuperclass, 프록시 개념 정리 본문

JPA

JPA / @MappedSuperclass, 프록시 개념 정리

참치깡 2025. 10. 15. 14:38
728x90

 

@MappedSuperclass

 

- 추상 클래스랑 비슷한데, @Entity는 실제 테이블과 매핑되지만 @MappedSuperclass는 실제 테이블과는 매핑되지 않는다.

- 단순히 매핑 정보를 상속할 목적으로만 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// @MappedSuperclass로 추상 클래스를 생성
@MappedSuperclass
public abstarct class BaseEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
 
    private String name;
 
}
 
// 1. 전혀 상관없는 Member 클래스
// 부모에게 상속받은 id 속성의 컬럼명을 MEMBER_ID로 재정의한다.
@Entity
// 1. 1개 속성만 변경할때 @AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID"))
// 2. 2개 이상의 매핑 정보를 재정의 할때
@AttributeOverrides({
    @AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID")),
    @AttributeOverride(name = "name", column = @Column(name = "MEMBER_NAME"))
})
public class Member extends BaseEntity {
    
    // ID 상속
    // NAME 상속
    private String email;
}
 
// 2. 전혀 상관없는 Seller 클래스
@Entity
public class Seller extends BaseEntity {
    
    // ID 상속
    // NAME 상속
    private String shopName;
}
 
cs

 

- BaseEntity에는 객체들이 주로 사용하는 공통 매핑 정보를 정의한다.

 

- 자식 엔티티들은 상속을 BaseEntity의 매핑 정보를 물려받는다.

 

부모로 부터 받은 매핑 정보를 재정의 하려면

 

1. @AttributeOverrides나 @AttributeOverride 사용

 

연관관계를 재정의 하려면

 

2. @AssociationOverrides나 @AssociationOveride 사용

 

- 그러나 실무에서는 사용을 잘 하지 않는다. (애초에 이걸 사용하면 그냥 설계가 잘못된 것)

 

정리

 

1. @MappedSuperClass는 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용한다.

 

2. @MappedSuperClass로 지정한 클래스는 @Entity가 아니기 때문에 em.find나 JPQL에서 사용할 수 없다.

 

진정한 상속관계 매핑은  https://chamchicoder.tistory.com/278

 

JPA / 상속관계 매핑 (SINGLE_TABLE, JOINED, TABLE_PER_CLASS)

객체지향에서는 클래스 상속을 통해 공통 속성을 물려주지만, 관계형 데이터베이스는 상속 개념이 없다. 그래서 JPA는 상속 구조를 테이블 구조로 변환하기 위해 3가지 전략을 사용한다. 1. 단일

chamchicoder.tistory.com

 

 

프록시

 

- 프록시란 실제 엔티티 대신 사용되는 가짜 객체이다.

- JPA가 엔티티(데이터베이스)를 즉시 로딩하지 않고, 나중에 필요할 때(DB 접근이 필요할 때) 실제 데이터를 

  가져오기 위해 사용하는 객체이다.

 

언제 만들어질까?

 

- 프록시는 지연 로딩(Lazy Loading) 시점에 생성된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
public class Member {
    @Id
    private Long id;
    private String name;
 
    @ManyToOne(fetch= FetchType.LAZY)
    private Team team; // 지연 로딩 관계
}
 
// 사용
 
Member member = em.find(Member.class , 1L);
Team team = member.getTeam(); // 프록시 객체만 생성되고, 실제 엔티티 객체가 생성되지는 않는다.
 
-> 진짜 Team이 아니라, Team을 상속받은 프록시 클래스 생성
-> 내부에 실제 Team을 로드하는 로직이 들어 있음
-> 필요할 때만 DB 접근해서 진짜 엔티티를 초기화함
 
 
cs

 

 

프록시의 동작 과정

 

1. 지연 로딩 시점

: member.getTeam() 호출 시, 프록시 객체가 반환된다 (DB 접근 X)

 

2. 초기화 시점

: 프록시의 메서드(team.getName() or Hibernate.initialize(team)) 호출 시 내부에서 

  영속성 컨텍스트에 실제 Entity를 만들어달라고 요청하고 해당 프록시가 실제 생성된

  엔티티의 메소드를 상속받아서 해당 데이터를 실행시킨다.

 

3. 초기화 이후

: 이제 프록시는 실제 Team 엔티티처럼 동작한다. (데이터는 캐싱됨)

 

주요 메서드...

 

em.getReference(EntityClass, id) : 프록시 객체를 직접 얻음(DB 접근 안 함)

 

em.find(EntityClass, id) : 실제 엔티티를 조회한다 (DB 접근 함)

 

entityManagerFactory.getPersistenceUnitUtil().isLoaded(entity)

: 프록시가 초기화되었는지 확인(맞으면 true, 아니면 false)

 

Hibernate.initialize(entity) : 프록시 객체를 강제로 초기화

(Hibernate 전용, JPA는 공식으로 지원하는 메서드가 없다)

 

프록시 주의사항

 

1. Team team1 = em.find(Team.calss, 1L);

   Team team2 = em.getReference(TEam.class, 1L);

   System.out.println(team1 == team2) //false 가 뜰 수 있다.

 

->  실제 엔티티랑 프록시 엔티티랑 동일성(==)비교가 실패할 수 있다.

->  항상 식별자 기반(equals)으로 비교해야 한다!!

 

team1.getId().equals(team2.getId()); //true

 

2. Team team = member.getTeam(); // 프록시 반환

   

   em.clear();

   em.close(); // 영속성 컨텍스트 종료

 

   team.getName(); // LazyInitializationException 발생

 

  -> DB 접근이 필요한데, 영속성 컨텍스트가 초기화 되어 없으니 예외 발생 

 

3. 프록시는 진짜 엔티티처럼 보이지만, 타입이 다르다

   그래서 타입 비교나, 실제 엔티티와의 equals, hashcode, 직렬화 시 문제가 생긴다.

 

  -> 해결책은 instanceof 사용, Entity로 바로 반환하지말고 DTO로 반환하기, fetch join을

     사용해야 한다.

728x90
Comments