JPA 스터디 - 4

한상우·2022년 11월 26일
0

JPA

목록 보기
4/7

JPA 7장

  1. 상속 관계 매핑
  2. @MappedSuperClass
  3. 복합키와 식별 관계 매핑
  4. 조인 테이블
  5. 엔티티 하나에 여러 테이블 매핑

상속관계 매핑


관계형 DB에서는 상속이라는 개념이 없음. 대신, 슈퍼타입 서브타입 관계라는 모델링 기법이 존재

슈퍼타입 서브타입 논리 모델을 테이블로 구현하는 방법 3가지

조인 전략

엔티티를 모두 테이블로 만들어, 자식 테이블이 부모 테이블의 기본키를 받아 기본키 + 외래키 로 사용하는 전략

이 때 객체는 타입이 있지만, 테이블은 타입의 개념이 없으므로 타입을 구분하는 컬럼이 필요

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
	
	@Id @GeneratedValue
	@Column(name = "ITEM_ID")
	private Long id;

	private String name;
}

@Entity
@DiscriminatorValue("A")
public class Album extends Item {
	private String artist;
}

@Entity
@DiscriminatorValue("M")
public class Movie extends Item {
	private String director;
}
  • 상속 매핑 Inheritance 사용,

  • 부모클래스에 구분 컬럼 지정 @DiscriminatorColumn(name = "DTYPE") 자식 테이블을 구분할 수 있음

  • Movie 엔티티를 저장하면 부모인 Item DTYPE에 M 이 들어감

  • 장점

    • 테이블이 정규화 됨
  • 단점

    • 조회시 조인이 많이 사용됨
    • 등록시 Insert 쿼리문 2번 실행

단일 테이블 전략

  • 테이블 하나만 사용, 구분 컬럼으로 어떤 자식 데이터가 저장되었는지 구분
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
  • 장점
    • 조회 성능이 빠르다
  • 단점
    • 자식 엔티티가 매필한 컬럼은 모두 null을 허용해야 함
    • 테이블이 커질 수 있다
  • 자식 엔티티에 @DiscriminatorValue 를 지정하지 않으면 기본 엔티티 이름을 사용하여 구분

구현 클래스마다 테이블 전략 (추천X)

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
  • 자식 엔티티마다 테이블을 만든다
  • 여러 자식테이블을 함께 조회할 때 성능이 느리고, 자식 테이블을 통합해서 쿼리하기 어렵다

MappedSuperclass


  • 부모클래스를 단순히 매핑 정보를 상속할 목적으로만 사용할 때 사용 (@Entity는 실제 테이블과 매핑되지만 @MappedSuperclass 는 매핑되지 않음)
@MappedSuperclass
public abstract class BaseEntity {
	
	@Id @GeneratedValue
	private Long id;

	private String name;
}

@Entity
public class Member extends BaseEntity {
	private String email;
}
  • 매핑정보를 재정의하려면 @AttributeOverride
  • 연관관계를 재정의하려면 @AssociationOverride
  • @MappedSuperclass로 지정한 클래스는 엔티티가 아니여서 em.find 나 JPQL 에서 사용 불가

복합키와 식별관계 매핑


복합키 비식별 관계 매핑

  • JPA 에서 복합키를 사용하려면 별도의 식별자 클래스를 만들어야 한다. (영속성 컨텍스트에 엔티티를 보관할 때 엔티티의 식별자를 키로 사용하기에)
  • 식별자 필드가 2개 이상이면 별도의 식별자 클래스에 equals 와 hashCode 를 구현해야 함

    @IdClass

    • 데이터베이스에 맞춘 방법 (테이블에 맞게 엔티티 매핑)

      @Entity
      @IdClass(ParentId.class)
      public class Parent {
      	@Id
      	@Column(name = "PARENT_ID1")
      	private String id1;
      
      	@Id
      	@Column(name = "PARENT_ID2")
      	private String id2;
      }
      public class ParentId implements Serializable {
      	
      	private String id1;
      	private String id2;
      
      	public ParentId() {}
      	public ParentId(String id1, String id2) {
      		this.id1 = id1;
      		this.id2 = id2;
      	}
      	
      	@Override
      	public boolean equals(Object o) { ... }
      
      	@Override
      	public int hashCode() { ... }
      }
    • 식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 함

    • Serializable 인터페이스를 구현해야 함

    • equals, hashCode 를 구현해야 함

    • 기본 생성자 있어야 함

    • 식별자 클래스는 public

@EmbeddedId

  • 객체지향적인 방법
@Entity
public class Parent {
	@EmbeddedId
	private ParentId id;
}
@Embeddable
public class ParentId implements Serializable {
	@Column(name = "PARENT_ID1")
	private String id1;

	@Column(name = "PARENT_ID2")
	private String id2;

	// equals, hashCode 구현
}

equals(), hashCode()

  • 영속성 컨텍스트는 엔티티의 식별자를 키로 사용해서 엔티티를 관리한다.
  • 식별자를 비교할 떄 equals() 와 hashCode() 를 사용한다
  • (최상단 Object 클래스의 equals 에 가기전에 적절히 오버라이딩 하면 참값이 나올 수 있음 - 이렇게 이해했는데 맞나요??)

복합키 식별관계 매핑

  • @IdClass
    • <예시>
    • @Id로 기본키 매핑 하면서 @ManyToOne 과 @JoinColumn으로 외래키를 같이 매핑
  • @EmbededId
    • 식별관계를 구성할 때 @Id 대신 @MapsId를 사용해야 함
    • @MapsId는 외래키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻

비식별 관계로 구현

  • 매핑도 쉽고 코드도 단순
  • 복합키가 없으므로 복합키 클래스를 안 만들어도 됨

결국 복합키는 비식별 관계로 구현하자는 내 생각

일대일 식별 관계

  • 자식 테이블의 기본키 값으로 부모 테이블의 기본키 값만 사용
  • 식별자가 단순히 컬럼 하나면 @MapsId를 사용하고 속성 값은 안써도 댐
    @MapsId
    @OneToOne
    @JoinColumn(name = "BOARD_ID")
    private Board board;

식별, 비식별 관계 장단점

  • 비식별관계를 더 선호

  • 식별관계는 부모 테이블의 기본키를 자식 테이블로 전파하면서 자식 테이블의 기본 키 컬럼이 점점 늘어남

  • 식별관계는 복합 기본키를 만들어야 하는 경우가 많음

  • 비즈니스 요구사항이 변경되었을 때 식별관계는 비식별관계보다 테이블 구조가 유연하지 못하다

  • 객체 관계 매핑 관점에서 식별관계는 복합 키 클래스가 따로 필요 비식별관계는 단순히 @GeneratedValue 사용

  • 추천: 필수적 비식별 관계를 사용 + 기본키 타입 Long 인 대리키

조인 테이블


조인 컬럼 사용

  • 외래키 컬럼을 사용해서 관리

조인 테이블 사용

  • 조인 테이블을 하나 추가하고 이 테이블에서 외래키를 가지고 연관관계를 관리

기본은 조인 컬럼을 사용하고 필요하다면 조인 테이블을 쓰자

일대일 조인 테이블

@Entity
public class Parent {
	@Id @GeneratedValue
	@Column(name = "PARENT_ID")
	private Long id;

	@OneToOne
	@JoinTable(name = "PARENT_CHILD",
						joinColumns = @JoinColumn(name = "PARENT_ID"),
						inverseJoinColumns = @JoinColumn(name = "CHILD_ID")
	)
}

@Entity
public class Child {
	@Id @GeneratedValue
	@Column(name = "CHILD_ID")
	private Long id;

}

다대일, 일대다 조인 테이블

조인 테이블의 컬럼중 다 인 컬럼에 유니크 제약 조건을 걸어야 한다

다대다 조인 테이블

조인 테이블의 두 컬럼을 합해서 하나의 복합 유니크 제약 조건

엔티티 하나에 여러 테이블 매핑

잘 사용하지 않음… 안볼래!!!!

JPA 8장


  • 프록시, 즉시로딩, 지연로딩
    • 객체는 객체 그래프로 연관된 객체들을 탐색하는데, 객체가 DB에 저장되어 있어 연관된 객체를 맘껏 탐색하기 어렵다. → 프록시라는 기술을 사용해 처음부터 객체를 DB에서 조회하는 것이 아니라, 실제 사용하는 시점에 DB에서 조회함
  • 영속성 전이와 고아 객체

프록시


회원 엔티티와 팀 엔티티가 서로 연관되어 있다고 가정했을 때, 회원의 정보만 알고 싶다면 굳이 팀 엔티티의 정보를 DB에서 조회할 필요가 없다. 결국 엔티티를 사용하는 시점에 DB에서 조회하는 것을 지연로딩 이라 한다

이 지연로딩을 할 때 실제 엔티티 객체 대신 DB 조회를 지연할 수 있는 가짜 객체가 필요한데 이를

프록시 객체 라고 한다.

기초

JPA 에서 식별자로 엔티티 하나를 조회할 때 EntitiyManager.find() 를 사용한다. 이 메소드는 영속성 컨텍스트에 엔티티가 없으면 DB에서 조회한다.

엔티티를 직접 조회하면 사용 유무에 관계없이 DB를 조회한다. 엔티티를 실제 사용하는 시점까지 조회를 미루고 싶으면 EntitiyManager.getReference() 를 사용하자

이 메소드를 호출하면 JPA는 DB를 조회하지 않고, 엔티티 객체도 생성하지 않는다. 대신 프록시 객체를 반환한다.

프록시 클래스는 실제 클래스를 상속 받아 만들어진다.

프록시 객체는 실제 객체에 대한 참조를 보관하고 있고, 프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.

프록시 객체 초기화

프록시 객체는 실제 사용될 때 DB를 조회해서 실제 엔티티 객체를 생성하는데, 이를 프록시 객체의 초기화라 한다.

MemberProxy가 있다 가정

  1. 프록시 객체에 member.getName() 호출
  2. 프록시 객체는 식제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청 (초기화)
  3. 영속성 컨텍스트는 DB 조회 - 실제 엔티티 객체 생성
  4. 프록시 객체는 실제 엔티티 객체의 참조를 보관
  5. 프록시 객체는 실제 엔티티 객체의 getName() 을 호출해 결과 반환
  • 프록시 객체는 처음 사용할 때 한 번만 초기화 된다
  • 프록시 객체가 초기화되면 프록시 객체를 통해 실제 엔티티에 접근 가능
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 getReference 를 해도 프록시가 아닌 실제 엔티티 반환

프록시와 식별자

  • 엔티티를 프록시로 조회할 때 식별자 값을 파라미터로 전달한다. Team team = em.getReference(Team.class, “team1”); 프록시 객체는 식별자 값을 가지고 있으므로 식별자 값을 조회하는 getId() 를 할 때 프록시를 초기화하지 않는다. (엔티티 접근 방식을 Property로 설정한 경우만)
  • 프록시는 연관관계를 설정할 때 유용하다. 연관관계를 설정할 때 식별자 값만 사용하므로 프록시를 사용하면 DB 접근 횟수를 줄일 수 있다.
  • PersistenceUnitUntil.isLoaded(Entity) 를 통해 프록시 인스턴스의 초기화 여부 확인 가능

즉시로딩과 지연로딩


프록시 객체는 연관된 엔티티를 지연로딩할 때 사용한다.

즉시로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회.

지연로딩 : 연관된 엔티티를 실제 사용할 때 조회한다.

즉시로딩

  • @ManyToOne(fetch = FetchType.EAGER)
  • em.find(Member.class, “member1”) 을 하게 되면 즉시로딩이 걸려있기에 팀도 함께 조회
  • 이때 JPA 구현체는 즉시로딩을 최적화 하기 위해 조인 쿼리를 사용한다.

지연로딩

  • @ManyToOne(fetch = FetchType.LAZY)
  • em.find(Member.class, “member1”) 을 하게 되면 회원만 조회, 팀은 조회하지 않음
  • 회원의 team 맴버 변수에 프록시 객체를 넣어둔다

지연로딩을 기본으로 사용하다가 두 엔티티간 호출이 잦을 때 즉시로딩을 사용

컬렉션 래퍼

  • 하이버네이트는 엔티티를 영속 상태로 만들 때 엔티티에 컬렉션이 있으면 컬렉션을 관리할 목적으로 원본 컬렉션을 하이버네이트가 제공하는 내장 컬렉션으로 변경 - 컬렉션에 대한 프록시 역할
  • 컬렉션에서 실제 데이터를 조회할 때 DB 를 조회해서 초기화한다.
    • team.getMembers() - 초기화 x
    • team.getMembers().get(0) - 초기화 o

JPA 기본 전략

@ManyToOne, @OneToOne : 즉시로딩

@OneToMany, @ManyToMany : 지연로딩 (컬렉션을 로딩하는 거은 비용이 많이 들고, 너무 많은 데이터를 로딩할 수 있기에)

영속성 전이 CASCADE


  • 특정 엔티티를 영속성 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 CASCASE 사용
  • 부모만 영속성 상태로 만들면 자식 까지 한 번에 영속 상태 할 수 있음

고아 객체


  • JPA 부모 엔티티와 연결이 끊어진 자식 엔티티를 자동으로 삭제 하는 기능 제공
  • 부모 엔티티 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티 자동 삭제 (orphanRemoval = true)
  • orphanRepoval 은 @OneToOne, @OneToMany 에서 사용 가능
profile
안녕하세요 ^^

0개의 댓글