Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

JAN's History

스프링과 JPA를 이용한 웹개발_연관관계 본문

스프링

스프링과 JPA를 이용한 웹개발_연관관계

JANNNNNN 2024. 3. 18. 11:09

연관관계

  • 연관관계 종류
    • One-To-One(일대일)
    • One-To-Many(일대다)
    • Many-To-One(다대일)
    • Many-To-Many(다대다)
  • Primary Key (PK) : 테이블 간의 행을 고유하게 식별하는 데에 사용되는 열 ex) ID
  • Foreign Key (FK) : 한 테이블의 열이 다른 테이블이 PK와 관련된 경우
  • ex) 주문테이블의 ID를 FK로 사용해 주문테이블과 사용자 테이블의 관계를 참조한다.

관계형 데이터베이스에서는 일대다에서 "다" 쪽이 FK를 가진다.

그래서 자연스럽게 "다" 쪽이 ownership을 가져서 자연스럽게 두개의 연관관계의 주인이 된다.

  • One-To-Many : 포스트 1개당 댓글을 많이 남길 수 있기 때문
  • One-To-One : 포스트 1개당 게시글에 대한 추가정보는 1개이기 때문
  • Many-To-Many : 포스트는 여러개의 해시태그를 가질 수 있고, 반대로 하나의 태그는 여러개의 포스트에 포함될 수 있기 때문
    • 그래서 다대다 관계에서는 중간에 Join Table, 중간다리 역할을 하는 테이블을 보통 가진다
    • post와 tag를 잇는 테이블이라 이 둘을 합친 post_tag 테이블처럼, 이름을 합쳐 짓는 경우가 많다.
    • 하지만 일반적으로 중간테이블을 둬서 다대다 연관관계보다는, 일대다 연관관계로 쓰는 경우가 많다.

다대일 연관관계

주문내역은 여러개의 아이템을 포함하므로 다대일

예시

  • 외래키(FK)보통 "다"쪽에 있기 때문에 Item Entity가 FK를 가지고 있는 것이 자연스럽다.
  • 그래서 Item에서 order를 가지고 있는 것이 좋고, 그 order를 PurchaseOrder Entity에서 mappedBy로 사용한다.
  •  @ManyToOne
    • 나의 엔티티를 기준으로 반대편 엔티티와의 연관성
    • 현재 Item은 "다"쪽이므로 다대일 연관관계를 위해 사용 

➡️다대일 양방향 연관관계에서는 OwnerShip을 정의해줘야한다. 이는 FK를 관리할 주체를 정의해야하한다는 것이고, 관리할 주체를 정의하는 방법은 주인이 아닌 mappedBy를 지정해주면 된다.

그러나 item에 purchase_order_id(FK)가 정의되어있지 않다.

⚠️ FK는 Main class에 ownership을 가진 "다"부분에서 FK를 setOrder로 설정해줘야한다.

결과물

FetchType 

특정 엔티티를 조회할 때 연관된 엔티티도 함께 조회할지를 결정

  • FetchType.LAZY : 연관된 엔티티는 가져오지 않음
  • FetchType.EAGER : 내가 명시하지 않아도 연관된 엔티티도 함께 조회
  • ex) item을 조회하는데 purchaseorder까지 들고오는 것.

예시 - FetchType.LAZY

LAZY

➡️findItem을 생성하고, 또 getOrder().getUserName()으로 UserName을 찾아 select문이 2번 실행된다.

EAGER

➡️ 길어서 잘 보이진 않지만 join을 애초에 먼저 실행해서 select문이 1번 실행된다.

주의사항

1. 일대다 단방향으로만 연관관계를 매핑하면 안된다.

  • ➡️외래키가 없는 "일"쪽에서 외래키가 있는 "다"쪽을 관리하는 구조.
  • SQL UPDATE문이 추가적으로 실행되는 문제가 발생하기 때문
  • 또한 Hibernate는 예상치 못한 테이블(association table)을 생성하며 더 많은 SQL이 발생함

2. OneToMany, ManyToMany에서 "다"쪽이 너무 많은 연관관계의 경우 연관관계 매핑을 지양하자.

  • ex)사용자 로그➡️사용자는 1명인데 로그가 너무 많이 찍히는 경우,
  • "다"쪽을 전부 메모리에 올리는 것은 위험하고 시간도 많이 걸려 효율적이지 못하다.
  • 이러한 경우 다대일 단방향 연관관계를 맺거나 굳이 연관관계를 맺지 않도록 하자.
  • 연관 엔티티를 읽는 경우 JPQL(직접 연관 엔티티를 찾는 SQL을 실행) 끊어 읽는 것이 좋다.

3. fechType은 LAZY을 기본적으로 사용하자.

  • oneToMany에서는 LAZY가 default 값이지만, manyToOne은 defalut값이 EAGER이다.
  • 그렇기 때문에 명시적으로 manyToOne을 사용할 때는 LAZY로 바꿔줘야한다.
  • ➡️하나의 엔티티를 조회할 떄 연관된 엔티티를 함께 조회되는 것은 큰 문제가 아니지만 여러 엔티티를 한 번에 조회할 때 모든 연관된 엔티티를 조회하는 것은 다른 문제이다.
    • 사용자 A를 조회하면서 A의 구매내역을 함께 조회하는 것과 1000명의 사용자를 조회할 때 모든 사용자의 구매 내역을 조회하는 것은 다른 문제
  • EAGER로 할 경우, JPQL을 사용할 때 N+1문제가 발생

4. N+1문제

  • 쿼리 1개를 날리면서 추가적으로 N개의 쿼리가 나가는 것
  • 엔티티 상태를 데이터베이스에 적용하는 시점은 트랜잭션이 종료되는 시점
  • 그러나 JPQL은 실행 즉시 쿼리가 실행된다.
  • DB관점에서는 실행된 쿼리가 JPA를 통해 전달되었는지, 엔티티가 서로 연관관계와 fech타입을 가졌는지 알지 못한다.
  • 일단 쿼리로 특정 엔티티만 조회했는데 JPA가 결과를 받아 해석하니 "EAGER로 설정된 엔티티가 있네?"라고 해석해 뒤늦게 추가 쿼리가 나감(+N)
  • JPQL에서는 패치조인으로 해결가능함,

5. 양방향 연관관계에서는 helper methods를 사용하라

  • 앞서 언급했던 것처럼 order에는 item을 넣었는데, item에는 order을 까먹고 넣지 못했을 수 있다.
  • 이러한 실수를 줄이기 위해 한 쪽의 연관관계 상태가 변경되면 다른 쪽도 이를 반영하도록 하는 것!