먹었으면 뇌를 쓰자

JPA 학습 내용을 정리한 로드맵 - 2. 연관관계/2.1 다대일 연관관계 본문

Framework/JPA

JPA 학습 내용을 정리한 로드맵 - 2. 연관관계/2.1 다대일 연관관계

뇌이비 2022. 12. 19. 21:46

 

1. JPA란 무엇인가
     1.1 기본 CRUD
     1.2 기초 지식

2. 연관관계
     2.1 다대일 연관관계 
     2.2 일대다 연관관계 

3. 상속
     3.1 상속관계
     3.2 CASCADE

4. SQL
     4.1 JPQL
     4.2 Querydsl

 

 

2.  연관관계

 

연관관계에는 4가지 종류가 있다.

 

일대일(One to One) ->  게시물/게시물 정보 

일대다(One to Many) ->  게시물/댓글

다대일(Many to One) ->  댓글/게시물

다대다(Many to Many) ->  게시물/중간 테이블/태그 

 

 

테이블끼리는 FK를 통해 양방향 연관관계를 설정할 수 있다.

그러나 객체끼리는 참조를 통해 단방향 연관관계를 설정한다.

따라서 객체끼리 양방향 연관관계를 설정하려면 서로 걸어줘야 한다. 

 

 

객체는 서로 참조를 걸어줘야 연관관계가 형성된다.

 

 

 

 

 

2.1  다대일 연관관계

 

- 엔티티 작성하기

- 다대일 연관관계 실행하기

 

 

 

 

<엔티티 작성하기>

 

Item.class (many) <-> PurchaseOrder.class (one)

여러 개의 아이템을 하나의 주문 목록서로 만든다.

RDB에서 FK는 다 쪽이 관리하므로, 아이템이 FK를 관리한다. 

 

 

<Item.class> (many)


@Entity
public class Item {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;

@ManyToOne(fetch = FetchType.LAZY) // 엔티티 조회 시, 연관 엔티티 함께 조회하지 않는다
@JoinColumn(name="PURCHASE_ORDER_ID") // FK로 사용할 연관관계 엔티티의 필드명
private PurchaseOrder order; // 일 쪽을 매핑할 order 필드를 생성한다

 

 

@ManyToOne 어노테이션은 FetchType을 지정한다.

FetchType은 해당 엔티티와 연관 엔티티를 함께 조회할지 여부를 결정하는 것이다.

 

FetchType.Lazy 연관 엔티티를 함께 조회하지 않는다.

FetchType.Eager 연관 엔티티를 함께 조회한다. 

 

ToOne 연관관계의 페치타입 디폴트는 Eager 이다. 

그러나 @ManyToOne에서 Eager 페치타입은 지양해야 한다.

 

예를 들어, 1명의 사용자를 조회할 때 주문 목록을 함께 조회하는 것은 괜찮다.

하지만 100만 명의 사용자를 조회할 때 그들의 주문 목록이 함께 조회된다면... 

 

또한 Eager 페치타입은 N+1 문제를 발생시키기도 한다.

쿼리 1개가 실행되면서, 추가적으로 N개의 쿼리가 발생하는 문제다. 

 

데이터베이스는 엔티티가 어떤 연관관계와 페치타입을 가지고 있는지 모른다.

그래서 데이터베이스에서는 일단 쿼리 1개로 특정 데이터가 조회된다.

나중에 JPA가 결과를 받아왔는데, 알고보니 Eager 페치타입이 있던 것이 발견된다. 

뒤늦게 추가 N개 쿼리가 데이터베이스로 날아간다. 

 

 

다대일 연관관계에서 다 쪽의 페치타입은 LAZY로 설정하자.

 

 

 

<PurchaseOrder.class> (one)


@Entity
public class PurchaseOrder {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "PURCHASE_ORDER_ID")
private Long id;
private String userName;

@OneToMany(mappedBy = "order") // 다 쪽의 order 필드에 의해 매핑되었다
private List<Item> items = new ArrayList<Item>(); // 다 쪽에서 만들어진 데이터 담을 객체 생성한다

 

 

 

 

 

<다대일 연관관계 실행하기>

 

 

<Main.class>

// 메인 클래스에서 Item, PurchaseOrder 엔티티를 읽고 쓸 수 있도록 
// 각 엔티티별로 미리 getter and setter를 만들어둔다.


Item item1 = new Item();
item1.setName("치킨");
Item item2 = new Item();
item2.setName("치즈볼");

*다 쪽인 아이템의 객체를 만들어 name 필드를 채워준다.



PurchaseOrder order = new PurchaseOrder();
order.setUsername("kim");
order.getItems().add(item1);
order.getItems().add(item2);

*일 쪽인 주문 목록의 객체를 만들어 userName 필드를 채워준다. 
*엔티티에서 만들어둔 items 객체에 .add 메소드로 item1,2를 매핑한다. ★양방향 매핑 



item1.setOrder(order);
item2.setOrder(order);

*엔티티에서 만들어둔 매핑 컬럼 order를 통해 객체 order를 매핑한다. ★양방향 매핑
*이름이 같은 건 우연이야~




em.persist(order);
em.persist(item1);
em.persist(item2);

*order 객체를 먼저 실행한 뒤 item1과 item2 객체를 실행해야 한다.
*FK의 관리 주체가 다 쪽인 Item이기 때문이다.
*만약 일 쪽인 PurchaseOrder 객체가 만들어지지 않은 상태로 냅다 Item 객체부터 실행하면,
*나중에 PurchaseOrder 객체에 매핑하러 다시 가야하기 때문에 2번의 UPDATE 쿼리가 나간다.

 

 

메인 클래스를 실행할 때 가장 중요한 점은 뭐니뭐니해도 양방향 매핑이다.

order에 Item1, Item2를 매핑하는 작업1과 

Item1,2에 order를 매핑하는 작업2가 둘 다 이루어져야 하는 것이다.

 

이 때 한 쪽에 헬퍼 메소드를 작성해서 

양방향 매핑을 깜빡하지 않도록 대비할 수 있다.  

 

 

<PurchaseOrder.class>


@Entity
public class PurchaseOrder {
...

@OneToMany(mappedBy = "order") 
private List<Item> items = new ArrayList<Item>(); 


public void addItem(Item item){ // Item과의 양방향 까먹지 말아라.
   this.items.add(item); // 우리 쪽 items에 Item에서 만들어진 객체 담아서 매핑해라.
   item.SetOrder(this); // Item에서 만들어진 객체는 order 필드로 우리 꼭 매핑해라.
 }
 
 
 
 
 <Main.class>
 
 Item item3 = new Item();
 item3.setName("감자튀김");
 order.addItem(item3);
 *이처럼 item3이 추가될 때, order 객체는 PurchaseOrder 엔티티에서 만든 헬퍼 메소드를 호출해 사용.
 *작업1(order->item1,2)과 작업2(item1,2->order) 어느 한 쪽 빼먹지 않고 양방향 설정된다.

 

 

 

양방향 매핑을 까먹지 말자. 헬퍼 메소드를 작성해두자. 
Comments