2022년 5월 11일 TIL

yshjft·2022년 5월 11일
0

데브코스 TIL

목록 보기
31/45
post-thumbnail

연관관계 매핑

객체 연관관계(참조) vs 테이블 연관관계(외래키)

데이터 중심 설계에는 객체 연관관계가 반영되어 있지 않다(참조를 사용하고 있지 않다). 따라서 객체 그래프 탐색을 할 수 없다.

방향

  • 단방향 참조

    class Member {
        private long id;
        private List<Order> orders;
    }
    
    class Order {
        private String id;
    }

    한쪽 객체에서만 참조

  • 양방향 참조

    class Member {
        private long id;
        private List<Order> orders;
    }
    
    class Order {
        private String id;
        private Member member;
    }

    양쪽 객체에서 서로 참조

연관관계 주인

public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    
    ...
    
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
}

public class Order {
	
    ...
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id", referencedColumnName = "id")
    private Member member;
}
  • 객체를 양방향 연관관계로 만들면, 연관관계의 주인을 정해야 한다.
  • 연관관계 주인만이, 외래키를 등록 수정 삭제 할 수 있다. (주인이 아닌쪽은 읽기만 가능)
  • FK를 가지고 있는 곳이 연관관계 주인이다.

객체 그래프 탐색

객체 참조를 통해 서로를 가져오는 것을 객체 그래프 탐색이라고 한다.

JPA 엔티티 객체 관계 매핑

@JoinColumn(name = "member_id", referencedColumnName = "id")
  • @JoinColumn
    외래 키를 매핑 할 때 사용하는 어노테이션으로서 만약 @JoinColumn을 명시하지 않는다면 필드명 + “_” + 참조하는 테이블의 기본 키(@Id) 컬럼명 전략에 따라 외래 키를 매핑합니다.

  • 연관관계 편의 메서드
    양방향 연관관계를 형성할 때 객체 상태를 고려하여 양쪽에 모두 값을 가지도록 설정한다. 그리고 이를 위해 사용하는 것을 연관관계 편의 메서드라고 한다.

@Entity
@Table(name = "member")
public class Member {

    ...

    @OneToMany(mappedBy = "member")
    private List<Order> orders;

	public void addOrder(Order order) {
        order.setMember(this);
    }
}


@Entity
@Table(name = "orders")
public class Order {
	
    ...

	@ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id", referencedColumnName = "id")
    private Member member;


	// 연관관계 편의 메소드
	public void setMember(Member member) {
        if(Objects.nonNull(this.member)) {
            this.member.getOrders().remove(this);
        }

        this.member = member;
        member.getOrders().add(this);
    }
}

고급 매핑

상속 관계 매핑

JOINED

@Entity
@Table(name = "item")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Item {
    ....
}

@Entity
public class Food extends Item {
    private String chef;
}

@Entity
public class Car extends Item {
    private long power;
}

@Entity
public class Furniture extends Item {
    private long width;
    private long height;
}
  • 추상 클래스 사용
  • @Inheritance(strategy = InheritanceType.JOINED)
  • 관리할 테이블이 늘어나기에 잘사용하지는 않는다

SINGLE_TABLE

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

@Entity
@DiscriminatorValue("FOOD")
public class Food extends Item{
    private String chef;
}

@Entity
@DiscriminatorValue("CAR")
public class Car extends Item{
    private long power;
}

@Entity
@DiscriminatorValue("FURNITURE")
public class Furniture extends Item{
    private long width;
    private long height;
}
  • DTYPE을 이용하여 엔티티가 어떤 테이블에 매핑이 되는지 찾는다.
  • 주로 이 방법을 사용한다.

@MappedSuperclass

  • 해당 어노테이션이 적용된 클래스가 엔티티가 되는 것은 아니다. 어노테이션이 적용된 클래스를 상속하는 엔티티는 상위 클래스의 값이 테이블에 추가로 매핑된다.
  • 공통으로 사용되는 필드들에 사용한다.
@MappedSuperclass
public class BaseEntity {
    @Column(name = "created_by")
    private String createdBy;
    
    @Column(name = "created_at", columnDefinition = "TIMESTAMP")
    private LocalDateTime cratedAt;
}

@Entity
@Table(name = "orders")
public class Order extends BaseEntity {
	...
}

식별자 클래스

복합 Primary Key가 있는 경우 사용한다.

@IdClass

  • Serializable 인터페이스를 구현해야 한다.

  • eqauls, hashCode를 구현해야 한다.

  • 기본 생성자가 있어야 한다.

  • 식별자 클래스는 public 이어야 한다.

  • 필드 이름이 같아야 한다.

  • 비추, 객체지향스럽지 않다.

    @EqualsAndHashCode
    @NoArgsConstructor
    @AllArgsConstructor
    public class ParentId implements Serializable {
        private String id1;
        private String id2;
    }
    
    @Getter
    @Setter
    @Entity
    @IdClass(ParentId.class)
    public class Parent {
        @Id
        private String id1;
        @Id
        private String id2;
    }

@EmbeddedId

  • Serializable 인터페이스를 구현해야 한다.

  • eqauls, hashCode를 구현해야 한다.

  • 기본 생성자가 있어야 한다.

  • 식별자 클래스는 public 이어야 한다.

  • @Embeddable 애노테이션이 있어야 한다.

  • 강추, 객체지향스럽다.

    @Getter
    @Setter
    @EqualsAndHashCode
    @NoArgsConstructor
    @AllArgsConstructor
    @Embeddable
    public class ParentId2 implements Serializable {
        private String id1;
        private String id2;
    }
    
    @Getter
    @Setter
    @Entity
    public class Parent2 {
        @EmbeddedId
        private ParentId2 id;
    }

프록시와 연관관계

프록시 객체

  • 객체 탐색을 자유롭게 하기위해 필요한 시점에 쿼리를 날려 엔티티화 한다.
  • fetch = FetchType.LAZY를 이용해 가져오는 것은 다 프록시 객체이다.
  • 프록시 객체는 처음 사용할 때 한번만 초기화 된다.
  • 프록시 객체가 초기화되면, 프록시 객체를 통해서 실제 엔티티에 접근 할 수 있다.
  • 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 준영속 상태의 프록시를 초기화하면 LazyInitializationException 예외가 발생한다.

LAZY

  • @OneToMany, @ManyToMany는 기본이 지연 로딩

EAGER

  • @ManyToONe, @OneToOne 기본이 즉시 로딩이다. 따라서 반드시 fetch = FetchType.LAZY를 설정 해주어야 한다.
  • JPQL에서 N+1 무제가 일어날 수 있기 때문에 반드시 지연 로딩을 사용해야 합니다.
    • 쿼리 1개에 N개의 쿼리가 추가되는 것
    SELECT * FROM order;

참고) PROXY 객체인지 아닌지 확인하는 방법

emf.getPersistenceUnitUtil().isLoaded(member)

영속성 전이(CASCADE)

  • 특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 영속상태로 만들고 싶을때 사용
  • parent와 child 관계에서 사용할 수 있다.
    • 단 parent가 child를 단일로 소유하고 있어야 한다.
    • ex) 첨부 파일
  • 편의성 제공을 목적으로 한다.

CASCADE의 종류

  • ALL : 모두 적용(영속 + 삭제)
  • PERSIST :영속
  • REMOVE : 삭제
  • MERGE : 병합
  • REFRESH : REFRESH
  • DETACH : DETACH
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems;

...

Order order = em.find(Order.class, uuid); // 영속 상태

OrderItem item = new OrderItem(); // 비영속상태
item.setQuantity(10);
item.setPrice(1000);

order.addOrderItem(item); // item이 영속성 전이를 통해 영속 상태로 바뀌었다

transaction.commit(); // flush()

CASCADE & 고아 객체

고아 객체

  • 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능
  • 참조하는 곳이 하나일 때 사용해야한다.
  • @OneToMany, @OneToOne에서 사용한다.
@Entity
@Table(name = "member")
public class Member extends BaseEntity {
    ...

    @OneToMany(mappedBy = "member", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();

	...
}


// 회원 조회 -> 회원의 주문 까지 조회
Member findMember = entityManager.find(Member.class, 1L);
findMember.getOrders().remove(0); // order를 제거한다. (고아객체)
profile
꾸준히 나아가자 🐢

0개의 댓글