No-ah98
Noah
No-ah98
전체 방문자
오늘
어제
  • 분류 전체보기 (40)
    • CS (7)
      • Java (7)
    • shell (1)
    • 개발 실수 (1)
    • 웹 (1)
    • Git (1)
    • 부트캠프 (1)
    • 링크 (0)
    • 오류 (0)
    • 일정 (0)
    • 객체지향 (4)
    • CodingTest (6)
    • TIL (2)
    • JPA (7)
    • JAVA (0)
    • 기타 (0)
    • Spring (5)
    • 알고리즘 (1)
      • 백준 (1)
      • 프로그래머스 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 문자 뒤집기
  • 연관관계 편의 메서드 필요성
  • 연관관계 매핑
  • 엔티티 매핑
  • @RequestParam
  • 데이터 셋팅
  • 멋사 백엔드
  • 연관관계
  • Git 정리
  • 문자열
  • form 공백 체크
  • 팰린드롬
  • Javascript 공백 체크
  • @PathVariable vs @RequestParam
  • 2xn타일링
  • 캡슐화
  • @Controller
  • @RequestBody vs @ModelAttribute
  • 문장 뒤집기
  • @RequestBody@ModelAttribute 차이
  • @RequestBody
  • 11726
  • @RestController
  • 중복문자제거
  • VO
  • @ModelAttribute
  • 백준
  • 프로그래머스
  • 멋쟁이사자처럼 백엔드 스쿨
  • 중복제거
  • DTOvsVO
  • @PathVariable vs @RequestParam 차이
  • 백엔드 스쿨
  • Entity
  • spring data jpa
  • @Controller vs @RestController
  • JPA데이터셋팅
  • JPA
  • 연관관계 편의 메서드
  • bash shell 기본 명령어
  • 양방향 연관관계
  • 멋쟁이사자처럼
  • 타일링
  • 계층형 댓글
  • 객체지향
  • 영속성 관리
  • 깃 정리
  • 해시
  • 대댓글
  • @PathVariable

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
No-ah98

Noah

다양한 연관관계 매핑
JPA

다양한 연관관계 매핑

2022. 3. 31. 15:17

목차

  • 연관관계 매핑시 고려사항 3가지
  • 다대일 [N:1]
  • 일대다 [1:N]
  • 일대일 [1:1]
  • 다대다 [N:M]

연관관계 매핑시 고려사항 3가지

  • 다중성
  • 단방향, 양방향
  • 연관관계의 주인 

 

  • 다중성
    • JPA에서는 다중성을 위한 다양한 어노테이션 제공
    • 이는 DB와 매핑하기 위해 존재
    • 그래서 데이터베이스 관점에서 다중성을 기준으로 고민
    • 다대일 : @ManyToOne
    • 일대다 : @OneToMany
    • 일대일 : @OneToOne
    • 다대다 : @ManyToMany (실무에서 사용하면 안됨)
    • 다중성을 고민하다가 풀리지 않으면 대칭성을 고려하자.
    • 다중성의 관계들은 대칭성을 다 가지고 있다. ex) 회원과 팀 - 팀과 회원 둘다를 고려하면 쉬워 진다.
  • 단방향, 양방향
    • 테이블
      • 외래 키 하나로 양쪽 조인 가능
      • 사실 방향이라는 개념이 없음
    • 객체
      • 참조용 필드가 있는 쪽으로만 참조 가능 
      • 한쪽만 참조하면 단방향
      • 양쪽이 서로 참조하면 양방향 (정확히 얘기하면 단방향이 2개)
  • 연관관계의 주인
    • 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음
    • 객체 양방향 관계는 A->B, B->A 처럼 참조가 2군데
    • 객체 양방향 관계는 참조가 2군데 있음. 둘중 테이블의 외래키를 관리할 곳을 지정해야 함
    • 연관관계의 주인 : 외래 키를 관리하는 참조
    • 주인의 반대편: 외래 키에 영향을 주지 않음, 단순 조회만 가능

다대일 [N:1]

  • 다대일 단방향
    • JPA에서 가장 많이 사용하는 연관관계
    • 테이블에서 다대일에서 "다"에 외래키가 존재해야 됨. 그렇지 않으면 잘못된 DB 설계
    • 객체에서 다대일 관계중 "다"인 Member에서 Team을 단방향으로 참조하고 싶을 때, 테이블에서 외래키가 있는 Member에서 Team으로 연관관계 매핑(@JoinColumn)을 하면 됨.
      • 물론 단방향 이기 때문에 양방향으로 조회는 안됨. ( Team -> Member 조회 X) 
    • 다대일의 반대는 일대다
    • 외래 키가 있는 쪽이 연관관계의 주인
    • 양쪽을 서로 참조하도록 개발

  • 다대일 단방향 매핑
    • JPA에서 다대일 단방향은 @ManyToOne으로 매핑
    • @JoinColumn은 외래키를 매핑할 때 사용
public class Member {
  ...
  
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
  
  ...
}

 

  • 다대일 양방향
    • 다대일 단방향 매핑을 한 후, 반대쪽에서 조회가 필요한 경우 사용
    • 반대쪽에서 일대다 매핑 후, 객체 컬렉션을 추가 해주면 됨
    • 위와 같이 양방향으로 한다고 해서 DB에 영향을 전혀 주지 않음.
      • 단순히 DB에서는 외래키 하나로 Join을 통하여 양쪽을 조회할 수 있기 때문에
      • 객체는 이러한 차이점을 해결하기 위함.
    • 외래 키가 있는 쪽이 연관관계의 주인
    • 양쪽을 서로 참조하도록 개발 

반대쪽에서 일대다 매핑 @OneToMany을 사용 

  • 양방향 일때는 자신이 어디에 매핑 되어 있는지에 대한 정보를 명시해야한다. mappedBy
  • 그러면 Team 객체는 Member 객체의 teams 필드에 매핑 됐다는 의미 전달을 확실히 할 수 있다.
public class Team {
    ...

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
    
    ...
}

정리

외래키가 있는쪽이 연관관계의 주인

양방향일때는 연관관계의 주인이 아닌 반대쪽에서 mappedBy를 통해 어디에 매핑됐는지를 명시 


일대다 [1:N]

일대다 [1:N]

  • 일대다 관계에서는 일이 연관관계의 주인
  • 즉, 일쪽에서 외래키를 관리하겠다는 의미
  • 표준스펙에서는 이를 지원 하지만, 실무에서는 별로 사용안하기 때문에 권장은 안한다.

일대다 단방향 매핑

  • 팀과 멤버가 일대다 관계
  • Team이 Members를 가지는데, Member 입장에서는 Team을 참조하지 않아도 된다라는 설계가 나올 경우 사용
  • 그러나 DB 테이블 입장에서 보면, 무조건 일대다의 다쪽에 외래키가 들어간다.
  • Team에서 members가 바뀌면, DB의 Member 테이블에 UPDATE SQL 실행 
    • 성능 이슈 

@Entity
public class Team {
    ...
    
    @OneToMany // Member와 일대다 관계
    @JoinColumn(name = "TEAM_ID") // 외래키 관리
    private List<Member> members = new ArrayList<>();
    
    ...
}

 

Main.java

...
Member member = new Member();
member.setUsername("MemberA");
em.persist(member);

System.out.println("-----멤버 저장-----");
Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member);
em.persist(team);
System.out.println("-----팀 저장-----");

tx.commit();

 

log를 통해 실제 발생한 쿼리를 보면 커밋 시점에   create one-to-many row로 시작되는 주석과 함께 Member 테이블을 업데이트 하는 쿼리가 발생한다. 

Hibernate: 
    /* insert hello.jpa.Member
        */ 
        insert 
        into
            Member
            (id, age, createdDate, description, lastModifiedDate, roleType, name) 
        values
            (null, ?, ?, ?, ?, ?, ?)
-----멤버 저장-----
Hibernate: 
    /* insert hello.jpa.Team
        */
        insert 
        into
            Team
            (id, name) 
        values
            (null, ?)
-----팀 저장------
Hibernate: 
    /* create one-to-many row hello.jpa.Team.members */ 
    update
        Member 
    set
        TEAM_ID=? 
    where
        id=?

일대다 단방향 정리 - 문제점과 실무적인 해결 방안

  • 일대다 단방향은 "일"이 연관관계의 주인
  • 테이블 일대다 관계는 "다"쪽에 외래키가 존재
  • 이러한 객체와 테이블의 패러다임 차이 때문에 객체의 반대편 테이블의 외래키를 관리하는 특이한 구조
  • @JoinColumn을 사용 안하면 조인 테이블 방식(중간 테이블)을 사용하기 때문에 꼭 사용해야 됨. 
  • 일대다 단방향 매핑의 단점
    • 엔티티가 관리하는 외래 키가 다른 테이블에 있음
    • 연관관계 관리를 위해 추가로 UPDATE SQL 실행 -> 성능 이슈 발생 
  • 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자.

 

 

일대다 양방향 정리

    • 이런 매핑은 공식적으로 존재X
    • @ManyToOne과 @JoinColumn을 사용해서 연관관계를 매핑하면, 다대일 단팡향 매핑이 됨. 그런데 반대쪽 Team에서 이미 일대다 단방향 매핑이 설정되어 있기 때문에, 두 테인티티 모두 테이블 외래키(FK)를 관리하는 상황이 발생
    • 이를 방지하고자 @JoinColumn(insertable=false, updatable=false)와 같이 설정해서 양방향 매핑처럼 읽기 전용으로 만듦
    • 그냥.. 다대일 양방향을 사용하자.😪

 


일대일 [1:1]

일대일 관계

  • 일대일 관계는 그 반대도 일대일
  • 주 테이블이나 대상 테이블 중에 외래 키 선택 가능
    • 주 테이블에 외래 키
    • 대상테이블에 외래 키
  • 외래 키에 데이터베이스 유니크(UNI) 제약조건 추가

일대일 : 주 테이블(Member)에 외래 키 단방향

  • 회원이 딱 하나의 락커를 가지고 있는 상황
  • 반대로 락커도 회원 한명만 할당 받을 수 있는 비즈니스 적인 룰이 있고, 이때, 둘의 관계는 일대일 관계이다.
  • 이러한 경우, 멤버를 주 테이블로 보고 주 테이블 또는 대상 테이블에 외래 키를 저장할 수 있다.
    • 단, 유니크 제약조건을 추가한 상태에서만.
  • 다대일[N:1] 단방향 관계 매핑과 JPA 어노테이션만 달라지고, 거의 유사하다.

일대일 : 주 테이블(Member)에 외래 키 양방향

  • 다대일[N:1] 양방향 매핑 처럼 외래키가 있는 곳이 연관관계의 주인
  • JPA @OneToOne 어노테이션으로 일대일 단방향 관계를 매핑하고, @JoinColumn을 넣어준다.
    • 여기까지만 하면 단방향 매핑
@Entity
public class Member {
    ...
        
    @OneToOne
    @JoinColumn(name = "locker_id")
    private Locker locker;
 
    ...
}
  • 반대편은 mappedBy 사용하여 양방향 관계를 매핑
    • mappedBy = "locker" 는 Member엔티티에 있는 Locker 필드와 매핑 되었다는 것을 의미
    • member 필드는 읽기 전용 필드이다.

 

 

@Entity
public class Locker {
    ...
        
    @OneToOne(mappedBy = "locker")
    private Member member;
}

 

일대일: 대상 테이블에 외래 키 단방향

  • 일대일관계에서 대상 테이블에 외래 키를 저장하는 단방향 관계는 JPA에서 지원하지 않는다.

일대일: 대상 테이블에 외래 키 양방향

  • 일대일 주 테이블에 외래 키 양방향 매핑을 반대로 뒤집었다고 생각하면 된다. 매핑 방법은 같다.
  • 주 테이블은 멤버 테이블이지만, 외래 키를 대상 테이블에서 관리하고 주 테이블의 locker 필드는 읽기 전용이 된다.

일대일 정리

  • 주 테이블에 외래 키
    • 주 테이블 : 많이 접근하는 테이블
    • 주 객체가 대상 객체의 참조를 가지는 것 처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음
    • 객체지향 개발자 선호 및 JPA 매핑이 편하다
    • JPA 매핑 편리
    • 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
    • 단점 : 값이 없으면 외래 키에 null 허용
  • 대상 테이블에 외래 키
    • 대상 테이블에 외래 키가 존재
    • 전통적인 데이터베이스 개발자 선호, null 허용해야 하는 문제가 없다.
    • 장점
      • 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
        • 멤버가 락커를 여러개 가지도록 비즈니스 룰이 변경될 경우 테이블 구조 유지하면서 유지보수 가능
    • 단점
      • 코드상에서 주로 멤버 엔티티에서 락커를 많이 액세스 하는데, 어쩔 수 없이 양방향 매핑 해야됨
      • 일대일 - 대상 테이블에 외래 키 단방향 매핑을 JPA에서 지원하지 않으므로, 단방향 매핑만 해서는 멤버 객체를 업데이트 했을 때 락커 테이블에 FK를 업데이트 할 방법이 없기 때문에 양방향 매핑을 해야 됨. 
      • 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨(프록시는 뒤에서 설명)

 


다대다 [N:M]

다대다 [N:M]

  • 실무에서 사용하면 안됨
  • 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없음
  • 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야 함

 

  • 객체는 컬렉션을 사용해서 객체 2개로 다대다 관계 가능 -> 테이블과의 차이점
    • ORM은 대안으로 아래 그림과 같이 객체의 다대다 관계와 테이블 다대다 관계를 일대다 다대일 관계로 풀어낸 것 두개의 차이를 연결해준다.

  • 코드)
    • @ManyToMany 어노테이션과
    • @JoinTable을 사용하여 연결 테이블을 지정하면 위 그림과 같이 매핑이 된다.
  • 다대다 관계 단방향
@Entity
public class Member {
    ...
    
    @ManyToMany
    @JoinTable(name = "member_product")
    private List<Product> products = new ArrayList<>();
    
    ...
}
  • 다대다 관계 양방향
    • 반대쪽에서 @ManyToMany 설정을 해주고, mappedBy 설정을 해줘야 한다.
@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "products")
    private List<Member> members = new ArrayList<>();
}

다대다 매핑의 한계

  • 편리해 보이지만 실무에서 사용X
  • 연결 테이블이 단순히 연결만 하고 끝나지 않음
    • 주문시간, 수량 같은 데이터가 들어올 수 있음.
    • 하지만 매핑 정보가 넣는게 가능하고 추가 데이터는 불가능
    • 중간 테이블이라 예상하지 못한 쿼리또한 나갈 가능성이 있음
    • 이러한 이유로 실무에서는 사용 안하는걸 권장.

다대다 한계 극복

  • 연결 테이블용 엔티티 추가(연결 테이블을 엔티티로 승격)
  • @ManyToMany -> @OneToMany, @ManyToOne
    • 쉽게 생각해보면 중간 테이블에서 다대일 관계 두 개로 매핑한다고 보면 됨 
  • 중간테이블의 PK는 독립적인 Id를 주는걸 권장
    • 연결된 두 테이블에 종속되지 않고 유연하게 개발할 수 있음.
    • 두 테이블의 FK를 PK로 잡을 경우, 운영중에 PK가 업데이트 될 수 있기 때문에

코드)

Member

@Entity
public class Member {
    ...
        
    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProducts = new ArrayList<>();

    ...
}

Product

@Entity
public class Product {

    ...

    @OneToMany(mappedBy = "product")
    private List<MemberProduct> members = new ArrayList<>();
    
    ...
}

MemberProduct

  • 연결 테이블을 엔티티로 승격 및 일대다 (@ManyToOne) 관계를 통해 연관관계 주인 설정 
@Entity 
@Getter @Setter
public class MemberProduct {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;
}

Reference

자바 ORM 표준 JPA 프로그래밍 

'JPA' 카테고리의 다른 글

프록시  (0) 2022.04.07
고급 매핑  (0) 2022.04.05
연관관계 매핑 기초  (0) 2022.03.29
엔티티 매핑  (0) 2022.03.24
영속성 관리  (0) 2022.03.23
    'JPA' 카테고리의 다른 글
    • 프록시
    • 고급 매핑
    • 연관관계 매핑 기초
    • 엔티티 매핑
    No-ah98
    No-ah98

    티스토리툴바