연관관계 매핑

엔티티 내 참고하고 싶은 엔티티 객체를 필드로 선언하고 연관관계에 대한 아노테이션 표기를 통해 엔티티 간의 연관관계를 객체에서도 표현할 수 있다.

@Entity
@Table(name = "orders")
@Getter
@Setter
public class Order {
    @Id
    @Column(name = "id")
    private String uuid;

	@ManyToOne // 엔티티와의 연관관계 정보
    private Member member; 
}

단방향 관계 VS 양방향 관계

두 개의 엔티티에서 한 엔티티만 다른 엔티티를 참조하는 것이 단방향 관계이고 양 쪽의 엔티티가 모두 서로 참조하는 것이 양방향 관계이다.

주문과 회원의 다대일 관계를 단방향 관계의 객체로 표현한 예시

@Entity
@Table(name = "member")
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(name = "name", nullable = false, length = 30)
    private String name;

    @Column(nullable = false, length = 30, unique = true)
    private String nickName;

    private int age;

    @Column(name = "address", nullable = false)
    private String address;

    @Column(name = "description", nullable = true)
    private String description;

}

@Entity
@Table(name = "orders")
@Getter
@Setter
public class Order {
    @Id
    @Column(name = "id")
    private String uuid;

    @Column(name = "order_datetime", columnDefinition = "TIMESTAMP")
    private LocalDateTime orderDatetime;

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus;

    @Lob
    private String memo;

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

주문과 회원의 다대일 관계를 양방향 관계의 객체로 표현한 예시

@Entity
@Table(name = "member")
@Getter
@Setter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(name = "name", nullable = false, length = 30)
    private String name;

    @Column(nullable = false, length = 30, unique = true)
    private String nickName;

    private int age;

    @Column(name = "address", nullable = false)
    private String address;

    @Column(name = "description", nullable = true)
    private String description;

    @OneToMany(mappedBy = "member") 
    // 연관관계 주인은 order.member
    private List<Order> orders;
}

@Entity
@Table(name = "orders")
@Getter
@Setter
public class Order {
    @Id
    @Column(name = "id")
    private String uuid;

    @Column(name = "order_datetime", columnDefinition = "TIMESTAMP")
    private LocalDateTime orderDatetime;

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus;

    @Lob
    private String memo;

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

연관관계 주인

엔티티 간의 연관관계가 양방향일 때 외래키를 관리할 객체인 연관관계의 주인을 지정해야 하고 오직 이 연관관계 주인만이 외래키를 등록, 수정, 삭제할 수 있고 아닌 쪽은 읽기만 수행이 가능하다. 테이블 중 FK를 가진 쪽이 연관관계 주인이 된다.

상속관계 매핑

조인테이블 전략

@Entity
@Table(name = "item")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Item {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private int price;
    private int stockQuantity;

    @ManyToOne
    @JoinColumn(name = "order_item_id", referencedColumnName = "id")
    private OrderItem orderItem;

    public void setOrderItem(OrderItem orderItem) {
        if (Objects.nonNull(this.orderItem)) {
            this.orderItem.getItems().remove(this);
        }

        this.orderItem = orderItem;
        orderItem.getItems().add(this);
    }
}

@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;
}

싱글테이블 전략

@Entity
@Table(name = "item")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private int price;
    private int stockQuantity;

    @ManyToOne
    @JoinColumn(name = "order_item_id", referencedColumnName = "id")
    private OrderItem orderItem;

    public void setOrderItem(OrderItem orderItem) {
        if (Objects.nonNull(this.orderItem)) {
            this.orderItem.getItems().remove(this);
        }

        this.orderItem = orderItem;
        orderItem.getItems().add(this);
    }
}

@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;
}

식별자 클래스

복합키를 가진 경우 별도의 식별자 클래스를 만들고 객체에 해당 클래스에 대해 식별자 클래스라는 표시를 해서 사용해야 한다.

  • Serializable 인터페이스를 구현해야 한다.
  • eqauls, hashCode를 구현해야 한다.
  • 기본 생성자가 있어야 한다.
  • 식별자 클래스는 public 이어야 한다.
@Getter
@Setter
@Entity
@IdClass(ParentId.class)
public class Parent {
    @Id
    private String id1;
    @Id
    private String id2;
}


@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class ParentId implements Serializable {
    private String id1;
    private String id2;
}

@EmbeddedId

@Embedded 관련 아노테이션을 통해 식별자 객체 클래스를 다음과 같이 사용하는 것도 가능하다.

@Getter
@Setter
@Entity
public class Parent2 {
    @EmbeddedId
    private ParentId2 id;
}

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

프록시

영속성 컨텐스트에서 관리하는 연관관계를 가진 엔티티는 해당 엔티티를 조회할 때 내부 연관관계의 엔티티까지 바로 조회하는 것이 아닌 빈 프록시 객체로 채워서 반환하고 내부 엔티티를 직접 사용할 때에 이르러서야 DB를 통해 가져오는 지연 로딩 전략을 사용하고 있다.

지연로딩

프록시 객체는 처음 사용할 때 영속성 컨텍스트에서 한번만 초기화되고 준영속 상태의 프록시를 초기화하면 LazyInitializationException 예외가 발생한다. 지연로딩을 사용한 경우 영속성 컨텍스트를 통해 값에 대한 조회가 가능하기 때문에 @Transactional이 붙은 메소드 안에서 프록시 객체에 대한 조회를 하지 않는 경우에는 에러가 발생한다.
(영속성 컨텍스트의 생명주기는 트랙잭션의 생명주기와 비슷하다.)

즉시로딩

연관된 엔티티를 실제 사용할 때 조회할 수 있는 전략으로 성능을 위해서 최소한으로 사용하는 것이 권장된다.

영속성 전이

연관된 엔티티의 대한 영속 상태에 대해 정의는 다음과 같은 종류에 대해서 가능하다.

  • ALL : 모두 적용
  • PERSIST :영속
  • MERGE : 병합
  • REMOVE : 삭제
  • REFRESH : REFRESH
  • DETACH : DETACH

고아객체

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능이다.

profile
ㅇㅅㅇ

0개의 댓글