엔티티 내 참고하고 싶은 엔티티 객체를 필드로 선언하고 연관관계에 대한 아노테이션 표기를 통해 엔티티 간의 연관관계를 객체에서도 표현할 수 있다.
@Entity
@Table(name = "orders")
@Getter
@Setter
public class Order {
@Id
@Column(name = "id")
private String uuid;
@ManyToOne // 엔티티와의 연관관계 정보
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;
}
@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;
}
복합키를 가진 경우 별도의 식별자 클래스를 만들고 객체에 해당 클래스에 대해 식별자 클래스라는 표시를 해서 사용해야 한다.
@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;
}
@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이 붙은 메소드 안에서 프록시 객체에 대한 조회를 하지 않는 경우에는 에러가 발생한다.
(영속성 컨텍스트의 생명주기는 트랙잭션의 생명주기와 비슷하다.)
연관된 엔티티를 실제 사용할 때 조회할 수 있는 전략으로 성능을 위해서 최소한으로 사용하는 것이 권장된다.
연관된 엔티티의 대한 영속 상태에 대해 정의는 다음과 같은 종류에 대해서 가능하다.
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능이다.