연관관계 매핑

굿거리·2023년 6월 15일
0

🧡 연관관계 매핑 종류와 방향

  • One to One : 일대일(1:1)
  • One to Many : 일대다(1:N)
  • Many to One : 다대일(N:1)
  • Many to Many : 다대다(N:M)

단방향과 양방향

  • 단방향 : 두 엔티티의 관계에서 한 쪽의 엔티티만 참조하는 형식
  • 양방향 : 두 엔티티의 관계에서 각 엔티티가 서로의 엔티티를 참조하는 형식

외래키를 가진 테이블이 관계의 주인이 되며, 주인은 외래키를 사용할 수 있으나 상대 엔티티는 읽는 작업만 수행할 수 있다.

1.1. 일대일 단방향 매핑

상품정보 -> 상품

@Entity
public class ProductDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
@OneToOne
@JoinColumn(name = "product_number")
private Product product;
}
  • @OneToOne
    다른 엔티티 객체를 필드로 정의했을 때 일대일 연관관계로 매핑하기 위해 사용
public @interface OneToOne {
	...
	FetchType[] cascade() default EAGER;
    ...
    boolean optional() default true;
    ...
}
  • fetch는 기본값으로 EAGER(즉시 로딩 전략) 채택
  • optional() 메서드는 기본값 true
    • false로 속성을 변경하면 product가 null값을 허용하지 않으며, sql이 left outer join에서 inner join으로 바뀌어 실행된다.
  • @JoinColumn
    • 매핑할 외래키를 설정.
    • name 속성을 사용해 칼럼명을 지정
    • referencedColumnName : 지정하지 않으면 대상 테이블의 PK로 자동 지정
    • foreignKey : 외래키를 생성하면서 지정할 제약조건 설정

1.2. 일대일 양방향 매핑

객체에서의 양방향은 양쪽에서 단방향으로 서로 매핑하는 것을 의미한다. 따라서

상품정보 <-> 상품

@Entity
public class Product {
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long number;
    private String name;
    private Integer price;
    private Integer stock;
    @OneToOne
    private ProductDetail productDetail;

그러나 양방향 매핑을 단방향 두번을 걸쳐 진행하면 left outer join이 두 번이나 수행되어 효율성이 떨어진다.

  • @OneToOne - mappedBy
    외래키를 사용할 때 어떤 객체가 주인인지 표시하는 속성
    • ToString을 실행할 때 순환참조 때문에 stackOverflowError가 발생. @ToString.Exclude를 통해 ToString을 제외한다.
@Entity
public class Product {
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long number;
    private String name;
    private Integer price;
    private Integer stock;
    @OneToOne(mappedBy = "product")
    private ProductDetail productDetail;

ProductDetail(주인) <-> Product

2.1. 다대일, 일대다 매핑

공급업체에 여러개의 상품이 납품되는 상황.

상품 테이블(N) <-> 공급업체(1)

💚 다대일 단방향 매핑

상품(N) -> 공급업체(1)

// 공급업체
@Entity
public class Provider {
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
}
// 상품
@Entity
public class Product {
	...
    @OneToOne(mappedBy = "product")
    @ToString.Exclude
    private ProductDetail productDetail
    
    @ManyToOne
    @JoinColumn(name = "provider_id")
    @ToString.Exclude
    private Provider provider;
}

💙 다대일 양방향 매핑

상품(N) <-> 공급업체(1)

// 공급업체
@Entity
public class Provider {
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @OneToMany(mappedBy = "provider", fetch = FetchType.EAGER)
    @ToString.Exclude
    private List<Product> productList = new ArrayList<>();
}

공급업체당 여러 상품이 포함될 수 있어 컬렉션 형식인 List를 사용한다.

  • @OneToMany
    • @JoinColumn을 사용하면 상대 엔티티에 외래키가 설정되어 @ToString에 순환참조가 발생해 Exclude로 제외.
    • fetch의 default가 Lazy이기 때문에 EAGER로 설정한다. Lazy 방식을 사용할 시 'no Session' 에러가 발생한다.
    • 그 이유는 product 단건 조회를 할 시 Product 필드 + Provider Proxy 객체가 생성되는데 Lazy상태에서는 Provider가 바로 초기화되지 않기 때문에 서비스의 Transaction에서 영속성의 생명주기가 끝나 영속성 상태가 종료되기 때문.

💜 일대다 단방향 매핑

상품(N) -> 상품 분류(1)

@Entity
public class Category {
	...
    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "category_id")
    private List<Product> products = new ArrayList<>();
}

위 상태에서 실행하면 상품 테이블의 id에 외래키가 추가된다.

💘@JoinColumn
다대일 상황에서는 name을 지정해주거나 원하는 컬럼을 지정하는 referencedColumnName을 사용하지 않는 이상 생략하더라도 매핑에 문제가 없다. 하지만 일대일, 일대다 단방향 매핑에서 생략하게 되면 중간 테이블로 Join 테이블이 생성되는 전략이 자동으로 채택된다.

profile
개발자를 향해

0개의 댓글