자바 ORM 표준 JPA 프로그래밍

[자바 ORM 표준 JPA 프로그래밍] 6장 - 다양한 연관관계 매핑

jny0 2023. 5. 15. 16:07

 

 

자바 ORM 표준 JPA 프로그래밍 | 김영한 - 교보문고

자바 ORM 표준 JPA 프로그래밍 | 자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA

product.kyobobook.co.kr

 

6장 - 다양한 연관관계 매핑

 

엔티티의 연관관계를 매핑할 때 고려해야 할 것 : 다중성, 방향, 연관관계의 주인

가능한 모든 연관관계

  • 다대일 : 단방향, 양방향
  • 일대다 : 단방향, 양방향
  • 일대일 : 주 테이블 단방향, 양방향
  • 일대일 : 대상 테이블 단방향, 양방향
  • 다대다 : 단방향, 양방향

 

1. 다대일

  • 다대일 관계와 일대다 관계는 항상 반대 방향
  • 외래키는 항상 다 쪽에 있고, 따라서 객체 양방향 관계에서 연관관계의 주인은 항상 다 쪽이다.

 

1.1 다대일 단방향 [N:1]

@Entity
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private String id;
    private String username;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

        //Getter, Setter ...
            ...
}

@Entity
public class Team {

    @Id
    @Column(name = "TEAM_ID")
    private String id;
    private String name;

        //Getter, Setter ...
            ...
}
  • 회원 → 팀 엔티티 참조 Member.team / 팀 → 회원 참조 불가
  • @JoinColumn(name = "TEAM_ID") 를 사용해서 외래 키 매핑

 

1.2 다대일 양방향 [N:1, 1:N]

@Entity
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private String id;
    private String username;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

        //Getter, Setter ...
            ...
}

public class Team {

    @Id
    @Column(name = "TEAM_ID")
    private String id;
    private String name;

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

        //Getter, Setter ...
            ...
}
  • 회원 ↔ 팀 양방향 참조 [Member.team](http://Member.team) Team.members
  • 외래키가 있는 다 쪽이 연관관계의 주인

 

2. 일대다

2.1 일대다 단방향 [1:N]

@Entity
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private String id;
    private String username;

        //Getter, Setter ...
        ...
}

public class Team {

    @Id
    @Column(name = "TEAM_ID")
    private String id;
    private String name;

    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<Member> members = new ArrayList<>();

        //Getter, Setter ...
        ...
}
  • 팀 엔티티의 Team.members 로 회원 테이블의 Team_ID 외래키를 관리
    • 일대다 관계에서 외래키는 항상 다쪽 테이블에 있는데, Members 엔티티에는 외래키를 매핑할 수 있는 참조 필드가 없기 때문에 반대편 테이블의 외래키를 관리
  • 일대다 단방향 관계를 매핑할 때 @JoinColumn 을 명시해야 함

 

단점

  • 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있음
  • 연관관계를 처리하기 위해 INSERT SQL 뿐만 아니라 UPDATE SQL을 추가로 실행해야 함
  • 이러한 단점 때문에 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용한다.

 

2.2 일대다 양방향 [1:N, N:1]

@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    private String username;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    private Team team;

        //Getter, Setter ...
        ...
}

@Entity
public class Team {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<Member> members = new ArrayList<>();

        //Getter, Setter ...
                    ...
}
  • 일대다 양방향 매핑은 존재 X → 다대일 양방향 매핑을 사용
    • 일대다 단방향 매핑 반대편에 같은 외래키를 사용하는 다대일 단방향 매핑을 읽기 전용으로 추가해서 일대다 양방향 매핑처럼 보이게 한다.
  • 일대다 단방향 매핑이 가지는 단점을 그대로 가지기 때문에 되도록 다대일 양방향 매핑을 사용해야 한다.

 

 

3. 일대일

  • 일대일 관계에서는 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래키를 가질 수 있고, 누가 외래키를 가질지 선택해야 한다.

 

3.1 주 테이블에 외래키

단방향

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String username;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
}

@Entity
public class Locker {

    @Id
    @GeneratedValue
    @Column(name = "LOCKER_ID")
    private Long id;

    private String name;
}

 

양방향

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String username;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker; 
}

@Entity
public class Locker {

    @Id
    @GeneratedValue
    @Column(name = "LOCKER_ID")
    private Long id;

    private String name;

    @OneToOne(mappedBy = "locker") 
    private Member member;
}
  • 연관관계의 주인은 Member.locker / 연관관계의 주인이 아닌 곳에 mappedBy 선언

 

3.2 대상 테이블에 외래키

 

단방향

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

 

양방향

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String username;

    @OneToOne(mappedBy = "member")
    private Locker locker;
}

@Entity
public class Locker {

    @Id
    @GeneratedValue
    @Column(name = "LOCKER_ID")
    private Long id;

    private String name;

    @OneToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
}

 

4. 다대다 [N:N]

  • 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다
    • 보통 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용한다.

  • 객체는 테이블과 다르게 객체 2개로 다대다 관계를 만들 수 있다. @ManyToMany 사용

 

4.1 다대다 : 단방향

@Entity
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;
    private String username;

    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT",
        joinColumns = @JoinColumn(name = "MEMBER_ID"),
        inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID")
    )
    private List<Product> products = new ArrayList<>();

}

@Entity
public class Product {

    @id
    @Column(name = "PRODUCT_ID")
    private String id;
    private String name;
}
  • @ManyToMany@JoinTable을 사용하여 연결 테이블을 바로 매핑

 

@JoinTable 속성

  • name : 연결 테이블을 지정
  • joinColumns : 현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정
  • inverseJoinColumns : 반대 방향인 상품과 매핑할 조인 컬럼 정보를 지정

 

 

4.2 다대다 : 양방향

@Entity
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;
    private String username;

    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT",
        joinColumns = @JoinColumn(name = "MEMBER_ID"),
        inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID")
    )
    private List<Product> products = new ArrayList<>();

}

@Entity
public class Product {

    @id
    @Column(name = "PRODUCT_ID")
    private String id;

    private String name;

    @ManyToMany(mappedBy = "products")
    private List<Member> members;
}
  • 역방향도 @ManyToMany 사용, 연관관계의 주인을 지정하기 위해 mappedBy 사용

 

 

4.3 다대다 : 매핑의 한계와 극복, 연결 엔티티 사용

  • 만약 연결 테이블에 주문수량과 주문 날짜 컬럼이 추가된 경우
  • 주문 엔티티나 상품 엔티티에는 추가한 컬럼들을 매핑할 수 없기 때문에 더 이상 @ManyToMany를 사용할 수 없다
  • 연결 테이블을 매핑하는 연결 엔티티를 만들고, 추가한 컬럼들을 매핑해야 한다.

 

@Entity
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    private String username;

    @OneToMany(mappedBy = "member")
    private List<MemberProduct> products = new ArrayList<>();
}

@Entity
public class Product {

    @id
    @Column(name = "PRODUCT_ID")
    private String id;

    private String name;
}

@Entity
@IdClass(MemeberProductId.class)
public class MemberProduct {

    @Id
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member; // MemberProductId.member와 연결

    @Id
    @ManyOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product; // MemberProductId.product와 연결

    private int orderAmount;
        ...

}
public class MemberProductId implements Serializable {

    private String member; // MemberProduct.member와 연결
    private String product; // MemberProduct.product와 연결

    // hashCode and equals
}
  • @Id와 는 @JoinColumn을 동시에 사용해서 기본 키와 외래 키를 한 번에 매핑
  • @IdClass를 사용해서 복합 기본 키를 매핑
    • JPA에서 복합 키를 사용하려면 별도의 식별자 클래스를 만들어야 함
    • @IdClass를 사용해서 식별자 클래스를 지정
    • 복합 기본 키는 Serializable 을 구현해야 하고, equalshasCode 메소드를 구현해야 함

 

  • 식별 관계
    • 부모 테이블의 기본 키를 받아서 자신의 기본 키+ 외래 키로 사용하는 것
    • 회원 상품은 회원과 상품의 기본키를 받아 자신의 기본키로 사용함

 

정리

  • 회원 상품은 회원의 기본키를 받아 자신의 기본키+ 회원과의 관계를 위한 외래키로 사용
  • 상품의 기본키를 받아 자신의 기본키+ 상품과의 관계를 위한 외래키로 사용
  • MemberProductId 식별자 클래스로 두 기본키를 묶어 복합 기본 키로 사용
  • 복합 키는 항상 식별자 클래스를 통해 엔티티를 조회해야 하기 때문에 복잡하다.

 

 

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

  • 새로운 기본키로는 데이터베이스에서 자동으로 생성해 주는 대리키를 Long 값으로 사용하는 것을 추천
    • ORM 매핑 시에 복합 키를 만들지 않아도 돼서 매핑을 간단히 완성할 수 있다

  • ORDER_ID라는 새로운 기본 키 생성, MEMBER_ID, PRODUCT_ID 컬럼은 외래키로만 사용
@Entity
public 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;

    private int orderAmount;

        ...
}
  • 식별 관계에 복합키를 사용하는 것보다 매핑이 단순하고 이해하기 편하기 때문에 대리키를 사용하는 것도 좋은 방법이다.
  • 식별자 클래스를 사용하지 않기 때문에 조회가 간단하다.
  • 받아온 식별자를 외래키로만 사용하고 새로운 식별자를 추가하는 것을 비식별관계라고 한다.