개발하는 쿠키
article thumbnail

엔티티의 연관관계를 매핑할 때는 다음을 고려해야 한다.

- 다중성 (다대일, 일대다, 일대일, 다대다)

- 단방향, 양방향

- 연관관계의 주인: 5장에서 연관관계의 주인인 테이블에서 외래키를 관리할 수 있었다.

관계에서 왼쪽에 있는 엔티티가 연관관계의 주인이다.

ex) 다대일 -> 다가 주인

일대다 -> 일이 주인

 

다대일

다대일 단방향 vs 다대일 양방향

다대일 단방향 / 다대일 양방향

위 그림에서 실선은 연관관계의 주인이고, 점선은 연관관계의 주인이 아니다.

Member 가 연관관계의 주인이고, Team 은 연관관계의 주인이 아니다.

 

다대일 단방향

다대일 단방향은 회원 엔티티에만 매핑 관련 코드를 넣으면 된다. 

다대일 양방향도 같은 회원 엔티티를 사용한다.

@Entity
public class Member {
    ....
    @ManyToOne
    @JoinColumn(name = "TEAM_ID") // 외래 키
    private Team team;
    ....
}

 

다대일 양방향

다대일 양방향은 팀 엔티티에도 매핑 관련 코드를 넣어야 한다.

@Entity
public class Team {
    ....
    @OneToMany(mappedBy = "team") // 연관관계의 주인이 아니므로 mappedBy를 사용한다.
    private List<Member> members = new ArrayList<>();
    ....
}

 

양방향 연관관계에서는 양쪽 엔티티가 무한루프에 빠지지 않도록 주의해야 한다.

@Entity
@ToString(exclude = "team") // 1. 무한루프 체크: team 필드 제외하고 스트링 출력
public class Member {
    ....
    @ManyToOne
    @JoinColumn(name = "TEAM_ID") // 외래 키
    private Team team;
    
    public void setTeam (Team team) {
        this.team = team;
        
        // 2. 무한루프 체크
        if(!team.getMembers().contains(this)){
            // Team 엔티티의 addMember 메서드를 호출한다.
            // addMember 내부에서는 member.setTeam(team); 코드가 있으므로
            // addMember 내부에서도 무한루프에 빠지지 않도록 체크해야 한다.
            team.addMember(this); 
        }
    }
    ....
}

 

 

일대다

일대다 단방향 vs 일대다 양방향

일대다 단방향 / 일대다 양방향

일대다 단방향

일대다 단방향 관계는 JPA2.0부터 지원한다.

일대다 단방향 관계는 Team.members 를 이용해서 Member 테이블의 TEAM_ID 외래키를 관리한다.

따라서 Team 정보를 저장할 때 Team 정보는 INSERT, Member 정보는 UPDATE 를 해야하는 번거로움이 생긴다.

그리고 최초에 Member 가 저장될 때는 TEAM_ID 외래키 정보는 null로 둔 채 저장되므로 테이블에 외래키 값이 있는 데이터, 없는 데이터가 생긴다.

 

따라서 일대다 단방향 매핑보다는 다대일 양방향 매핑을 권장한다.

일대다 양방향

일대다 양방향 매핑은 다대일 양방향 매핑을 이용해서 구현할 수 있다. 

일대다 양방향 매핑에서는 다 쪽이 연관관계의 주인이 아니므로 mappedBy를 사용해야 하지만,

외래키는 항상 다 쪽에 있으므로 @ManyToOne 어노테이션에는 mappedBy 속성이 없다.

따라서 다 쪽에 읽기 전용 @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) 어노테이션을 추가해야 한다.

 

Team 테이블은 일대다 단방향, 일대다 양방향 모두 동일하다.

@Entity
public class Team {
    ....
    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<Member> members = new ArrayList<>();
    ....
}

일대다 양방향일 때만 매핑 코드를 추가한다.

@Entity
public class Member {
    ....
    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    private Team team;
    ....

}
일대다 단방향과 비슷한 단점을 가지므로 일대다 양방향보다는 다대일 양방향 매핑을 사용하자.

일대일

회원(Member)은 사물함(Locker) 1개를 가질 수 있고,

각 사물함은 회원 1명이 가질 수 있다고 한다.

 

주 테이블에 외래 키

단방향 / 양방향

주 테이블인 Member에 외래키를 넣은 상태다.

외래 키를 객체 참조와 비슷하게 사용할 수 있다. Member.locker 를 Member 테이블의 LOCKER_ID 외래키로 바로 조인해서 조회할 수 있다.

 

Member 엔티티는 단방향, 양방향이 같다.

@Entity
public class Member {
    ....
    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
    ....
}

 

양방향인 경우 Locker 엔티티에  매핑 코드를 추가한다.

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


대상 테이블에 외래 키

단방향 / 양방향

단방향

단방향은 JPA에서 지원하지 않는다고 한다.

인프런 질문에 보면 김영한님도 정확한 이유는 모른다고 답변이 달려있다.

가능하지 않나...?

이 부분에 대해 공부하게 되면 추가 내용 적겠습니다.

양방향

@Entity
public class Member {
    ....
    @OneToOne(mappedBy = "member")
    private Locker locker;
    ....
}
@Entity
public class Locker {
    ....
    @OneToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
    ....
}
프록시의 한계로 외래 키를 직접 관리하지 않는 일대일 관계는 즉시로딩으로 실행된다.
Locker.member -> 지연로딩 가능
Member.locker -> 지연로딩 불가능, 즉시로딩으로 실행됨.

이 문제는 프록시 대신 bytecode instrumentation을 사용하면 해결된다.

다대다

N:M 다대다 연결 테이블

데이터베이스 테이블로는 다대다를 표현할 수 없다.

따라서 중간에 연결테이블을 추가한 형태로 설계해야 한다.

반면, 객체는 연결 객체에 추가되는 변수들이 없는 한, 연결 객체가 필요 없다.

 

다대다:단방향, 다대다:양방향 (연결 객체가 없는 버전)

 

다대다:단방향, 다대다:양방향은 같은 Member 엔티티를 사용한다.

@Entity
public class Member {
    ....
    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT", 
    joinColumns = @JoinColumn(name = "MEMBR_ID"),
    inverseJoinColmns = @JoinColumn(name = "PRODUCT_ID")
    )
    private List<Product> products = new ArrayList<>();
    ....
}

 

다대다:양방향은 Product엔티티에 매핑 정보를 추가한다.

@Entity
public class Product {
    ....
    @ManyToMany(mappedBy = "products")
    private List<Member> members;
    ....
}

 

다대다 연결 엔티티 사용

테이블, 연결 테이블에 필드 추가

@ManyToMany는 회원 상품 테이블에 주문수량, 주문 날짜 칼럼이 필요해질 때 사용할 수 없다.

이때는 연결 엔티티를 추가해야 한다.

@Entity
public class Member {
    ....
    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProducts;
    ....
}
@Entity
public class Product {
    ....
    // 매핑 코드 없음.
    ....
}
@Entity
@IdClass (MemberProductId.class) // 복합키, 식별 관계
pubic class MemberProduct {
    @Id
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
    
    @Id
    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
    
    ....
}

// 복합키를 사용하는 경우 복합키 클래스도 추가해야 한다.
public class MemberProductId implements Serializable {
    private String member;
    private String product;
    
    // hashcode and equals method
    // 기본 생성자 있어야 함.
    ...
}

 

복합키, 식별 관계의 한계

컬럼 1개만 사용해서 기본키를 만드는 것보다, 복합키를 사용하는 것은 추가 클래스를 만들어야 하고, 내부에 여러 메소드도 추가해야 하기때문에 복잡하다.

 

극복 방법

이 문제를 해결하기 위해 새로운 기본키를 추가할 수 있다.

 

다대다: 새로운 기본키 사용

다대다 새로운 기본키

Order 객체에 id 변수를 추가하면 된다.

@Entity
pubic class Order {
    @Id
    @GeneratedValue
    @Column(name = "ORDER_ID")
    private Long id; // 비식별 관계
    
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
    
    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
    
    ....
}
새로운 기본키 사용(비식별 관계) -> 이 방법이 새로운 ID 클래스를 추가하지 않아도 되므로 추천하는 방법이다.

정리

- 이번 장에서는 다대일,일대다, 일대일, 다대다 연관관계를 단방향, 양방향으로 매핑하는 방법에 대해 공부했다.

- 양방향 연관관계에서는 양쪽 엔티티가 무한루프에 빠지지 않도록 주의해야 한다.

- 일대다 단방향 매핑보다는 다대일 양방향 매핑을 권장한다.

- 일대다 단방향과 비슷한 단점을 가지므로 일대다 양방향보다는 다대일 양방향 매핑을 사용하자.

- 새로운 기본키 사용(비식별 관계) -> 이 방법이 새로운 ID 클래스를 추가하지 않아도 되므로 추천하는 방법이다.

 


참고자료

- https://www.yes24.com/Product/Goods/19040233 "자바 ORM 표준 JPA 프로그래밍-김영한"

반응형
profile

개발하는 쿠키

@COOKIE_

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!