고급 매핑, 프록시

mingsso·2023년 7월 4일
0

SpringBoot

목록 보기
10/11

상속 관계 매핑

조인 전략

엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키로 사용하는 전략 -> 조회할 때 조인을 자주 사용함

@Entity
@Inheritance(strategy = InheritanceType.JOINED)   // 상속 매핑은 부모 클래스에 @Inheritance를 사용해야 함
@DiscriminatorColumn(name = "DTYPE")   // 부모 클래스에 구분 컬럼 지정 (저장된 자식 테이블 구분)
public abstract class Item {

	@Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    
    private String name;
    private int price;
    ...
}

@Entity
@DiscriminatorValue("A")   // 엔티티를 저장할 때 구분 컬럼에 입력될 값 지정 
public class Album extends Item {

	private String artist;
    ...
}

장단점

  • 테이블이 정규화됨
  • 외래 키 참조 무결성 제약조건을 활용할 수 있음
  • 저장공간을 효율적으로 사용함
  • 조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있음
  • 조회 쿼리가 복잡함
  • 데이터를 등록할 INSERT SQL을 두 번 실행함

단일 테이블 전략

테이블을 하나만 사용하고, 구분 컬럼(DTYPE)으로 어떤 자식 데이터가 저장되었는지 구분함

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {

	@Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    
    private String name;
    private int price;
    ...
}

@Entity
@DiscriminatorValue("A")
public class Album extends Item { ... }

@Entity
@DiscriminatorValue("M")
public class Movie extends Item { ... }

@Entity
@DiscriminatorValue("B")
public class Book extends Item { ... }

장단점

  • 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
  • 조회 쿼리가 단순함
  • 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 함
  • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있음 -> 조회 성능이 오히려 느려질수도

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

일반적으로 추천하지 않는 전략

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {

	@Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    
    private String name;
    private int price;
    ...
}

@Entity
public class Album extends Item { ... }

@Entity
public class Movie extends Item { ... }

@Entity
public class Book extends Item { ... }

장단점

  • 서브 타입을 구분해서 처리할 때 효과적
  • not null 제약조건을 사용할 수 있음
  • 여러 자식 테이블을 함께 조회할 때 성능이 느림
  • 자식 테이블을 통합해서 쿼리하기 힘듦



@MappedSuperclass

부모 클래스는 테이블과 매핑하지 않고 부모 클래스를 상속받는 자식 클래스에게 매핑 정보만 제공하고 싶을 때 사용함

@MappedSuperclass
public abstract class BaseEntity {

	@Id @GeneratedValue
    private Long id;
    private String name;
    ...
}

@Entity
public class Member extends BaseEntity {

	private String email;
    ...
}

@Entity
public class Seller extends BaseEntity {

	private String shopName;
    ...
}

@MappedSuperclass로 지정한 클래스는 엔티티가 아니므로 em.find()나 JPQL에서 사용할 수 없음 (단순히 엔티티가 공통으로 사용하는 매핑 정보를 모아주는 역할)
-> 추상 클래스로 만드는 것을 권장함



복합 키와 식별 관계 매핑

DB 테이블 사이의 관계는 외래 키가 기본 키에 포함되는지 여부에 따라 식별 관계와 비식별 관계로 구분

식별 관계

부모 테이블의 기본 키를 내려받아서 자식 테이블의 기본 키 + 외래 키로 사용하는 관계

비식별 관계

부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키로만 사용하는 관계

비식별 관계는 외래 키에 NULL을 허용하는지에 따라 필수적 비식별 관계선택적 비식별 관계로 나눔

  • 필수적 비식별 관계 - 외래 키에 널 허용하지 않음, 연관관계를 필수적으로 맺어야 함
  • 선택적 비식별 관계 - 외래 키에 널 허용함, 연관관계를 맺을지 말지 선택할 수 있음

최근에는 비식별 관계를 주로 사용하고 꼭 필요한 곳에만 식별 관계를 사용하는 추세

복합 키 : 비식별 관계 매핑

둘 이상의 컬럼으로 구성된 복합 기본 키를 매핑하려면, 아래처럼 하면 될 것 같지만

@Entity
public class Hello {
	@Id
    private String id1;
    @Id
    private string id2;   // 매핑 오류 발생함
}

JPA에서 식별자를 둘 이상 사용하려면 별도의 식별자 클래스를 만들어야 함
-> 복합 키를 지원하기 위해 @IdClass@EmbeddedId 2가지 방법을 사용함



프록시

엔티티를 조회할 때 연관된 엔티티들이 항상 사용되는 것은 아님
-> 회원 엔티티를 조회할 때 팀 엔티티는 비즈니스 로직에 따라 사용되기도 하고 아니기도 함

  • JPA는 엔티티가 실제 사용될 때까지 DB 조회를 지연하는 방법을 제공함 (지연 로딩)
  • 지연 로딩 기능을 사용하려면 실제 엔티티 객체 대신에 DB 조회를 지연할 수 있는 가짜 객체가 필요한데, 이것을 프록시 객체라고 함


엔터티를 실제 사용하는 시점까지 DB 조회를 미루고 싶으면 EntityManager.getReference()를 사용하는데, 이 메소드는 DB 접근을 위임한 프록시 객체를 반환함

프록시 객체는 실제 객체에 대한 참조를 보관하고, 프록시 객체의 메소드(member.getName())를 호출하면 프록시 객체는 실제 객체의 메소드를 호출함

  • 프록시 객체는 처음 사용할 때 한 번만 초기화됨 -> 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근할 수 있음
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 DB를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환함
  • 초기화는 영속성 컨텍스트의 도움을 받아야 가능함 -> 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제가 발생함
// 준영속 상태와 초기화
Member member = em.getReference(Member.class, "id1");   // MemberProxy 반환 
transaction.commit();
em.close();   // 영속성 컨텍스트 종료 (member는 준영속 상태)

member.getName();   // 준영속 상태 초기화 시도 (에러 발생 = 영속성 컨텍스트가 없으므로)



즉시 로딩과 지연 로딩

즉시 로딩

엔티티를 조회할 때 연관된 엔티티도 함께 조회함
설정 방법 - @ManyToOne(fetch = FetchType.EAGER)

Member member = em.find(Member.class, "member1") 
// 회원을 조회하는 순간 팀도 함께 조회함
// 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리가 사용됨 -> 쿼리 1번으로 두 엔티티 조회

Team team = member.getTeam();



지연 로딩

연관된 엔티티를 실제 사용할 때 조회함
-> 연관된 엔티티를 프록시로 조회하고, 프록시를 실제 사용할 때 초기화하면서 DB를 조회함
설정 방법 - @ManyToOne(fatch = FetchType.LAZY)

Member member = em.find(Member.class, "member1");
// 회원만 조회하고 팀은 조회하지 않음

Team team = member.getTeam();  // team에 프록시 객체 넣음 
team.getName();   // 팀 객체 실제 사용 

추천하는 방법은 모든 연관관계에 지연 로딩을 사용하는 것
애플리케이션 개발이 어느 정도 완료단계에 왔을 때 실제 사용하는 상황을 보고 꼭 필요한 곳에만 즉시 로딩을 사용하도록 최적화하면 됨



영속성 전이: CASCADE

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용
-> 영속성 전이를 사용하면 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장할 수 있음

@Entity
public class Parent {
	...
    @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
    private List<Child> children = new ArrayList<Child>();
    ...
}

private static void saveWithCascade(EntityManager em) {
	
    Child child1 = new Child();
    Chile child2 = new Child();
    
    Parent parent = new Parent();
    child1.setParent(parent);
    child2.setParent(parent);
    parent.getChildren().add(child1);
    parent.getChildren().add(child2);
    
    // 부모 저장, 연관된 자식들 저장
    em.persist(parent);
}



고아 객체

JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하는데, 이것을 고아 객체 제거라고 함

@Entity
public class Parent {

	@Id @GeneratedValue
    private Long id;
    
    @OneToMany(mappedBy = "parent", orphanRemoval = true)
    private List<Child> children = new ArrayList<Child>();
    ...
}

단, 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이므로 참조하는 곳이 하나일 때만 사용해야 함
-> @OneToOne, @OneToMany에만 사용할 수 있음

profile
🐥👩‍💻💰

0개의 댓글