새소식

back/spring

Orm 표준 jpa 프로그래밍 (4) - 매핑, 프록시

  • -

 

 1. 상속관계 매핑

 

- 관계형 DB는 상속관계가 없음

- 슈퍼타입 / 서브타입 관계의 모델링 기법이 객체의 상속과 유사

- 상속관계 매핑: 객체의 상속과 구조와 DB의 슈퍼타입 / 서브타입 관계를 매핑

 

슈퍼타입 / 서브타입 논리 모델을 물리 모델로 구현하는 방법

1. @Inheritance(strategy=InheritanceType.XXX)

- 각각 테이블로 변환 => 조인 전략 (JOINED)

- 통합 테이블로 변환 => 단일 테이블 전략 (SINGLE_TABLE)

- 서브타입 테이블로 변환 => 구현 클래스마다 테이블 전략 (TABLE_PER_CLASS)

 

2. @DiscriminatorColumn(name=“DTYPE”)

 

3. @DiscriminatorValue(“XXX”)

 

 

조인 전략

- 장점: 테이블 정규화, 외래 키 참조 무결성 제약조건 활용 가능, 저장공간 효율화

- 단점: 조회시 조인을 많이 사용 => 성능 저하, 조회 쿼리가 복잡, 데이터 저장시 insert sql 2번 호출(이유: 자식 테이블과 부모 테이블에 저장되기 때문)

 

 

 

단일 테이블 전략

- 장점: 조인이 필요 없으므로 일반적으로 조회 성능이 빠름, 조회 쿼리가 단순함

- 단점: 자식 엔티티가 매핑한 컬럼은 모두 null 허용, 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있음 => 상황에 따라 조회 성능이 느려질 수 있음

 

 

 

구현 클래스마다 테이블 전략

- 추천 X

- 장점: 서브 타입을 명확하게 구분해서 처리할 때 효과적, not null 제약 조건 사용 가능

- 단점: 여러 자식 테이블을 함께 조회할 때 성능이 느림, 자식 테이블을 통합해서 관리 어려움

 

 

 

@MappedSuperclass

- 공통 매핑 정보가 필요할 때 사용(ex: id, name)

 

 

- 상속관계 매핑이 아니며, 엔티티/테이블과 매핑되지 않음, 부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공

>> 테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할, 주로 등록일 / 수정일 / 등록자 / 수정자 같은

전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용

- 조회 / 검색이 불가함 (em.find(BaseEntity)불가능)

- 직접 생성해서 사용할 일이 없으므로 추상 클래스로 생성 권장 

 

- @Entity 클래스는 엔티티나 @MappedSuperclass 로 지정한 클래스만 상속 가능

 

 

 

2. 프록시 

 

- em.find(): DB를 통해서 실제 엔티티 객체 조회

- em.getReference(): DB 조회를 미루는 가짜(프록시) 엔티티 객체 조회

 

프록시의 특징

- 실제 클래스를 상속 받아 만들어짐

- 실제 클래스와 겉모양이 같음

- 이론상, 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨

- 프록시 객체는 실제 객체의 참조(target)을 보관, 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출

//프록시 객체의 초기화
Member member = em.getReference(Member.class, “id1”);
member.getName();

- 프록시 객체는 처음 사용할 때 한번만 초기화

- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능

- 프록시 객체는 원본 엔티티를 상속받고 이로 인해 타입 체크시 주의해야 함( == 말고 instance of 이용 )

- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환

- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생함 

(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)

 

 

프록시 확인

// 프록시 인스턴스의 초기화 여부 확인
PersistenceUnitUtil.isLoaded(Object entity)

// 프록시 클래스 확인 방법
entity.getClass().getName() 출력(..javasist.. or HibernateProxy...)

// 프록시 강제 초기화
org.hibernate.Hibernate.initialize(entity);

// JPA 표준은 강제 초기화 없음
// 강제 호출: member.getName()

 

 

3. 즉시 로딩과 지연 로딩

// 지연 로딩 LAZY을 사용해서 프록시로 조회

@Entity
public class Member {
  @Id
  @GeneratedValue
  private Long id;
  @Column(name = "USERNAME")
  private String name;
@ManyToOne(fetch = FetchType.LAZY) //** @JoinColumn(name = "TEAM_ID")
  private Team team;
..
}

- 실제 엔티티를 사용하는 시점에 초기화(DB 조회)

 

 

// Member와 Team을 자주 함께 사용한다면? >> 즉시 로딩 EAGER를 사용해서 함께 조회

@Entity
public class Member {
  @Id
  @GeneratedValue
  private Long id;
  @Column(name = "USERNAME")
  private String name;
@ManyToOne(fetch = FetchType.EAGER) //** @JoinColumn(name = "TEAM_ID")
  private Team team;
..
}

 

 

 

- 실무에서는 '지연 로딩' 만 사용 !!

- 즉시 로딩을 적용하면 예상하지 못한 sql 문 발생

- 즉시 로딩은 JPQL에서 n+1 문제를 일으킨다.

- @ManyToOne / @OneToOne은 기본이 즉시 로딩이라서 LAZY로 설정해야 됨!!

- @OneToOne / @ManyToMany는 기본이 지연 로딩

 

 

지연 로딩 활용  

- 두 엔티티(테이블)이 자주 함께 사용 되면 즉시 로딩, 가끔 함께 사용되면 지연 로딩 이용

 

- 실무에서는 모든 연관관계에 지연 로딩 사용해야 함

- JPQL fetch 조인이나, 엔티티 그래프 기능을 사용

 

 

4. 영속성 전이 cascade

 

- 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속상태로 만들고 싶을 때 (ex: 부모 엔티티 저장할 때 자식 엔티티도 함께 저장)

- 영속성 전이는 연관관계를 매핑하는 것과는 상관 없음

- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공

@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)



 

cascade 의 종류

- ALL: 모두 적용

- PERSIST: 영속

- REMOVE: 삭제

- MERGE / REFRESH / DETACH

 

 

5. 고아 객체

 

- 고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제

// orphanRemoval = true 인 상태에서

Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
// 자식 엔티티를 컬렉션에서 제거

- 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능

- 참조하는 곳이 하나일 때 사용해야 함! => 특정 엔티티가 개인 소유할 때

- @OneToOne / @OneToMany 만 가능

 

개념적으로 부모를 제거하면 자식은 고아가 됨 >> 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거

 >> CascadeType.REMOVE처럼 동작

 

영속성 전이 + 고아 객체, 생명주기

- CascadeType.ALL + orphanRemoval=true

- 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거

- 두 옵션을 모두 활성화 하면 부모 엔티티를 통해 자식의 생명주기를 관리할 수 있음

- DDD(도메인 주도 설계)의 Aggregate root 개념 구현시에 유용

 

 

 

 

 

 

 

 

 





Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.