본문 바로가기
JPA

[JPA] 다양한 연관관계 매핑

by jinjin98 2022. 10. 31.

엔티티의 연관관계를 매핑할 때는 다음 3가지를 고려해야 합니다.

 

1. 다중성

다대일(@ManyToOne)

일대다(@OneToMany)

일대일(@OneToOne)

다대다(@ManyToMany)

다중성을 판단하기 어려울 때는 반대방향을 생각해보면 됩니다. ex) 일대다 -> 다대일

보통 다대일과 일대다 관계를 가장 많이 사용합니다.

 

2. 단방향, 양방향

테이블은 외래키 하나로 조인을 사용하므로 양방향으로 쿼리가 가능해서 방향이라는 개념이 없습니다.

반면에 객체는 참조용 필드를 가지고 있는 객체만 연관된 객체를 조회할 수 있습니다. 객체 관계에서 

한 쪽만 참조하는 것을 단방향 관계, 양쪽이 서로 참조하는 것을 양방향 관계라고 합니다.

 

3. 연관관계의 주인

데이터베이스는 외래키 하나로 두 테이블이 연관관계를 맺습니다. 따라서 테이블의 연관관계를 관리하는 지점은

외래키 하나입니다. 반면에 엔티티를 양방향으로 매핑하면 A -> B, B-> A  2곳에서 서로를 참조합니다.

이렇게 되면 객체의 연관관계를 관리하는 지점이 2곳이기 때문에 하나를 정해서 데이터베이스 외래키를 

관리하는데 이것을 연관관계의 주인이라고 합니다. 외래키를 가진 테이블과 매핑한 엔티티가 외래키를 관리하는게

효율적이므로 보통 이 곳을 연관관계의 주인으로 선택합니다. 주인이 아닌 방향은 외래키를 등록, 수정, 삭제

할 수 없으며 읽기만 가능합니다. 연관관계의 주인은 mappedBy 속성을 사용하지 않고 연관관계의 주인이 아니면

mappedBy 속성을 사용하며 속성값은 연관관계의 주인 필드 이름으로 설정합니다.

 

지금부터는 다중성과 단방향, 양방향을 고려해서 가능한 모든 연관관계를 하나씩 알아보겠습니다.

참고로 다중성은 왼쪽을 연관관계의 주인으로 정합니다. ex) 다대일 양방향 -> 다(N) 가 연관관계의 주인

 

다대일

 

다대일 관계의 반대 방향은 항상 일대다 관계이고 반대로 일대다 관계의 반대 방향은 다대일 관계입니다.

데이터베이스 테이블의 다대일 관계에서는 항상 다 쪽에 외래키가 있습니다. 따라서 객체 양방향 관계에서

연관관계의 주인은 항상 다쪽입니다.

 

다대일 단방향

 

 

@Entity
@Getter @Setter
public class Member{

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

    private String username;

    private int age;
    
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}

 

@Entity
@Getter @Setter
public class Team {

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

    private String name;
}

 

회원은 Member.team 으로 팀 엔티티를 참조가 가능하지만 반대로 팀에서는 회원을 참조할 수 있는 필드가

없습니다. 따라서 회원과 팀은 다대일 단방향 관계입니다.

@JoinColumn(name="team_id") 을 사용해서 Member.team 필드를 team_id 외래키와 매핑했습니다. 

따라서 Member.team 필드로 회원 테이블 team_id 외래키를 관리합니다.

 

다대일 양방향 

 

 

객체 연관관계에서 실선이 연관관계의 주인이고 점선은 연관관계의 주인이 아닙니다.

 

@Entity
@Getter @Setter
public class Member{

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

    private String username;

    private int age;
    
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
    
    public void setTeam(Team team) {
        this.team = team;
        if(!team.getMembers().contains(this))
            team.getMembers().add(this);
    }
}

 

@Entity
@Getter @Setter
public class Team {

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

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

 

다대일 단방향에서 팀에서도 회원을 참조할 수 있도록 members 필드를 추가했습니다. 필드를 추가한 것은 

객체 연관관계어서 양방향 참조를 하기 위한 것으로 데이터베이스에는 아무런 영향을 주지 않습니다.

양방향 연관관계에서는 항상 서로를 참조해야 하므로  회원 엔티티에 연관관계 편의 메서드인 setTeam() 메서드를

작성해줬습니다. 팀 엔티티에도 작성해서 양쪽에서 호출한다면 무한루프에 빠질 수 있으므로 회원 엔티티 한 곳에만

작성했습니다. 양방향 연관관계에서는 외래키가 있는 쪽이 연관관계의 주인입니다. 일대다와 다대일 연관관계는 항상

다(N) 에 외래키가 있습니다. 여기서도 다쪽인 MEMBER 테이블이 외래키를 가지고 있어 Member.team 이 연관관계의

주인입니다. 주인이 아닌  Team.members 는 주인이 아니라는 표시로 mappedBy 속성을 사용하며 외래키와  매핑된

Member 의 필드 team 을 값으로 넣어줍니다. JPA 에서 외래키를 관리할 때는 연관관계의 주인만 사용합니다.

주인이 아닌 Team.members 는 조회를 위한 JPQL 이나 객체 그래프를 탐색할 때 사용합니다.

 

̱ 일대다

 

일대다 관계는 다대일 관계의 반대 방향입니다. 엔티티를 하나 이상 참조할 수 있으므로 자바 컬렉션인

Colletion, LIst, Set, Map 중 하나를 사용해야 합니다.

 

일대다 단방향

 

 

 

@Entity
@Getter @Setter
public class Team {

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

    private String name;

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

 

@Entity
@Getter @Setter
public class Member{

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

    private String username;

    private int age;
}

 

하나의 팀은 여러 회원을 참조할 수 있는 관계입니다. 팀은 회원들을 참조하지만 회원은 팀을 참조하지 않으므로

둘의 관계는 단방향입니다. 일대다 단방향 관계를 매핑할 떄는 @JoinColumn 명시해야 합니다. 그렇지 않으면

JPA 는 연결 테이블을 중간에 두고 연관관계를 관리하는 조인 테이블 전략을 기본으로 사용해서 매핑합니다.

이렇게 하면 테이블이 하나 더 들어가 성능상 애매하고 운영하기가 쉽지 않습니다.

 

일대다 단방향 매핑의 단점

일대다 단방향 매핑의 단점은 매핑할 엔티티가 관리하는 외래키가 다른 테이블에 있는 것입니다. 매핑한 객체에

해당하는 테이블에 외래키가 있다면 엔티티의 저장관 연관관계의 처리를 INSERT SQL 한 번으로 끝낼 수 

있지만 다른 테이블에 외래키가 있으면 연관관계 처리를 위한 UPDATE SQL 을 추가로 실행해야 합니다.

나의 엔티티가 아닌 다른 엔티티에 외래키가 있오 다른 엔티티의 값을 변경하는 것이니 UPDATE SQL 을

실행하는 것이죠. 그렇기 때문에 일대다 단방향 매핑보다는 참조를 하나 더 넣더라도 다대일 단방향 매핑을

사용해야 합니다.

 

일대다 양방향

 

 

일대다 양방향 매핑은 존재하지 않습니다. 더 정확하게 말하면 양방향 매핑에서 @OneToMany 는 연관관계의 주인이

될 수 없습니다. 왜냐하면 관계형 데이터베이스의 특성상 일대다, 다대일 관계는 항상 다쪽에 외래키가 있기 때문입니다.

따라서 @OneToMany, @ManyToOne 둘 중에 연관관계의 주인은 항상 다 쪽에 @ManyToOne 를 사용한 곳입니다.

이런 이유로 @ManyToOne 에는 mappedBy 속성이 없습니다. 다중성은 왼쪽을 연관관계의 주인으로 정해 일대다에서

일 쪽이 연관관계의 주인으로 되면 다 쪽이 연관관계 주인이 아니므로 mappdeBy 속성을 사용해야 하는데 사용할 수가

없습니다. 그렇다고 일대다 양방향 매핑이 완전히 불가능한 것은 아니고 다 쪽의 엔티티에 같은 외래키를 사용하는 다대일

단방향 매핑을 읽기 전용으로 하나 추가하면 됩니다.

 

@Entity
@Getter @Setter
public class Team {

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

    private String name;

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

 

@Entity
@Getter @Setter
public class Member{

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

    private String username;

    @ManyToOne
    @JoinColumn(name = "team_id", insertable = false, updatable = false)
    private int age;
}

 

일대다 단방향 매핑 반대편에 다대일 단방향 매핑을 추가했습니다. 이 때 일대다 단방향 매핑과 같은 team_id 

외래키 컬럼을 매핑했습니다. 이렇게 되면 둘 다 연관관계의 주인이 되서 문제가 발생할 수 있습니다. 그래서

반대편인 다대일 쪽은 insertable = false, updatable = false 로 설정해서 읽기만 가능하도록 했습니다. 이렇게 하면

일대다 양방향과 기능이 똑같다고 할 수 있습니다. 일대다 양방향 매핑은 공식적으로 존재하지는 않지만 위 코드와

같이 직접 설정해줄 수 있습니다. 하지만 이렇게 복잡하게 일대다 양방향을 설계해서 사용할 바에는 다대일 양방향을

사용하는 것이 훨씬 간단하고 편한 방법입니다.

 

일대일

 

일대일 관계는 양쪽이 서로 하나의 관계만 가집니다. 예를 들어 회원은 하나의 사물함만 사용하고 사물함도 하나의 

회원에 의해서만 사용됩니다.

 

일대일 관계 특징

일대일 관계는 그 반대도 일대일 관계입니다.

테이블 관계에서 일대다, 다대일은 항상 다쪽이 외래키를 가집니다. 반면에 일대일 관계는 주 테이블이나 대상 테이블

둘 중 어느 곳이나 외래키를 가질 수 있습니다. 테이블은 주 테이블이든 대상 테이블이든 외래키 하나만 있으면 

양쪽으로 조회를 할 수 있습니다. 그리고 일대일 관계는 그 반대쪽도 일대일 관계이므로 주 테이블이나 대상 테이블중

누가 외래키를 가질지 선택해야 합니다.

 

1. 주 테이블에 외래키

주 객체가 다른 객체를 참조하는 것처럼 주 테이블에 외래키를 두고 대상 테이블을 참조합니다. 외래키를 객체 참조와

비슷하게 사용할 수 있어서 객체지향 개발자들이 선호합니다. JPA 도 주 테이블에 외래키가 있으면 좀 더 편리하게

매핑할 수 있습니다. 이 방법의 장점은 주 테이블이 외래키를 가지고 있으므로 주 테이블만 확인해도 대상 테이블과

연관관계가 있는지 알 수 있습니다. 단점은 값이 없으면 외래키에 null 이 적용됩니다. 

 

단방향

 

 

@Entity
@Getter @Setter
public class Member{

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

    private String username;

    private int age;

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

 

@Entity
@Getter @Setter
public class Locker {

    @Id
    @GeneratedValue
    @Column(name = "locker_id")
    private Long id;
    
    private String name;
}

 

일대일 관계이므로 객체 매핑에 @OneToOne 어노테이션을 사용했고 데이터베이스에는 locker_id 외래키에 유니크

제약 조건을 추가했습니다. 다대일 단방향과 비슷합니다. 

 

양방향

 

 

@Entity
@Getter @Setter
public class Member{

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

    private String username;

    private int age;

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

 

@Entity
@Getter @Setter
public class Locker {

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

    private String name;
    
    @OneToOne(mappedBy = "locker")
    private Member member;
}

 

양방향이므로 주인을 정해야합니다. MEMBER 테이블이 외래키를 가지고 있으므로 회원 엔티티에 있는 

Member.locker 가 연관관계의 주인입니다. 따라서 반대편 사물함 엔티티의 Locker.member 는 mappedBy 를

선언해서 연관관계의 주인이 아니라고 설정합니다.

 

2. 대상 테이블에 외래키

전통적인 데이터베이스 개발자들은 보통 대상 테이블에 외래키를 두는 것을 선호합니다.  이 방법의 장점은

테이블 관계를 일대일에서 일대다로 변경할 떄 테이블 구조를 그대로 유지할 수 있습니다. 단점은 프록시 기능의 한계로

지연로딩으로 설정해도 항상 즉시 로딩이 됩니다.

 

단방향

 

 

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

방법 형태로 매핑할 수 있는 방법도 없습니다. 이 때는 단방향 관계를 사물함에서 회원 방향으로 수정해 회원

테이블을 대상 테이블로 하거나, 양방향 관계로 만들고 사물함을 연관관계의 주인으로 설정해야 합니다.

 

양방향

 

 

@Entity
@Getter @Setter
public class Member{

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

    private String username;

    private int age;

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

 

@Entity
@Getter @Setter
public class Locker {

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

    private String name;

    @OneToOne(mappedBy = "locker")
    @JoinColumn(name = "member_id")
    private Member member;
}

 

일대일 매핑에서 대상 테이블에 외래키를 두고 싶으면 이렇게 양방향으로 매핑합니다. 주 엔티티인 회원 엔티티 대신

대상 엔티티인 사물함을 연관관계의 주인으로 만들어 LOCKER 테이블의 외캐리를 관리하도록 했습니다.

 

̱ 다대다

 

 

관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없습니다. 그래서 보통 다대다 관계를

일대다, 다대일 관계로 풀어내는 연결 테이블을 사용합니다. 회원과 상품의 관계로 예를 들어보면 여러 명의

회원이 하나의 상품을 주문할 수 있고, 여러 개의 상품이 한 명의 회원에게 주문될 수 있습니다. 따라서 회원

테이블과 상품 테이블만으로는 이 관계를 표현하지 못합니다.

 

 

그렇기 때문에 아래와 같이 중간에 연결 테이블을 추가해줘야 합니다. 회원과 주문 테이블 사이에 회원이 주문한

상품인 Member_Product 연결 테이블을 추가했습니다. 이 테이블을 사용해서 다대다 관계를 일대다, 다대일 관계로

풀어낼 수 있습니다.

 

 

그런데 객체는 테이블과 다르게 객체 2개로 다대다 관계를 만들 수 있습니다. 회원 객체에서 컬렉션을 사용해서

상품들을 참조하고 반대로 상품들도 컬렉션을 사용해 회원들을 참조하면 되는 것입니다. @ManyToMany 

어노테이션을 사용해서 이런 다대다 관계를 편리하게 매핑할 수 있습니다.

 

다대다 단방향

 

@Entity
@Getter @Setter
public class Member{

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

    private String username;

    private int age;

    @OneToOne
    @ManyToMany
    @JoinTable(name = "member_product", 
               joinColumns = @JoinColumn(name = "member_id"),
               inverseJoinColumns = @JoinColumn(name = "product_id"))
    List<Product> products = new ArrayList<>();
}

 

@Entity
@Getter @Setter
public class Product {

    @Id
    @Column(name = "member_id")
    private Long id;
    
    private String name;
}

 

회원 엔티티와 상품 엔티티를  @ManyToMany 로 매핑하고  @ManyToMany 와 @JoinTable 을 사용해서 연결 테이블을

바로 매핑했습니다. 따라서 회원과 상품을 연결하는 회원 상품 엔티티 없이 매핑을 완료할 수 있습니다.

name: 연결 테이블을 지정합니다.

joinColumns:  현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정합니다.

inverseJoinColumns:  반대 방향인 상품과 매핑할 조인 컬럼 정보를 지정합니다.

 

Member_Product 테이블은 다대다 관계를 일대다, 다대일 관계로 풀어내기 위해 필요한 연결 테이일 뿐입니다. 

@ManyToMany 로 매핑한 덕분에 다대다 관계를 사용할 떄는 이 연결 테이블을 신경 쓰지 않아도 됩니다.

 

저장

 

Product productA =  new Product();
product.setId("productA");
product.setName("상품A");
em.persist(product);

Member member1 = new Member();
member1.setId("member1");
member1.setAge(25);
member1.setUsername("member1);
member1.getProducts().add(productA);
em.persist(member1);

 

회원1과 상품A 의 연관관계를 설정했으므로 회원1을 저장할 떄 연결 테이블에도 갑이 저장됩니다.

위와 같이 코드를 작성하면 MEMBER_PRODUCT 테이블에도 값이 저장되는 INSERT SQL 문이 실행됩니다.

 

조회

 

Member member = em.find(Member.class, "member1");
List<Product> products = member.getProducts();
for (Product product : products) {
    System.out.println(product.getName());
}

 

엔티티를 순서대로 저장한 후에 탐색해보면 저장해두었던 상품1이 조회되는데 member.getProducts() 를 호출하면

아래와 같은 SQL 문이 실행됩니다.

 

SELECT * FROM MEMBER_PRODUCT MP
INNER JOIN PRODUCT P ON MP.PRODUCT_ID = P.PRODUCT_ID
WHERE MP.MEMBER_ID=?

 

실행된 SQL 문을 보면 연결 테이블인 MEMBER_PRODUCT 와 상품 테이블을 조인해서 연관된 상품을 조회합니다.

이렇게 @ManyToMany 어노테이션 덕분에 복잡한 다대다 관계를 어플리케이션에서 아주 단순하게 사용할 수 있습니다.

 

다대다 양방향

 

@Entity
@Getter @Setter
public class Member{

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

    private String username;

    private int age;

    @OneToOne
    @ManyToMany
    @JoinTable(name = "member_product",
            joinColumns = @JoinColumn(name = "member_id"),
            inverseJoinColumns = @JoinColumn(name = "product_id"))
    List<Product> products = new ArrayList<>();
    
    public void addProduct(Product product) {       
        products.add(product);
        product.getMembers().add(this);
    }
}

 

@Entity
@Getter @Setter
public class Product {

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

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

 

다대다 양방향에서는 반대쪽 방향도 @ManyToMany 을 사용합니다. 그리고 양쪽 중 원하는 곳에 mappdeBy 로

연관관계의 주인을 지정합니다. 여기서는 회원 엔티티를 연관관계의 주인으로 정해 상품 엔티티에 mappdeBy 를

사용했습니다. 또한 회원 엔티티에 연관관계 편의 메서드를 추가해줘 양방향 연관관계를 설정했습니다.

 

Product product = em.find(Product,class, "ProductA");
List<Member> members = product.getMembers();
for (Member member : members) {
    System.out.println(member.getUsername());
}

 

양방향 연관관계로 만들어서 product.getMembers() 를 사용해서 역방향으로도 객체 그래프를 탐색할 수 있습니다.

 

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

@ManyToMany 를 사용하면 연결 테이블을 자동으로 처리해주므로 도메인 모델이 단순해지고 여러 가지로 편리

합니다. 그런데 실무에서 사용하기에는 한계가 있습니다. 예를 들어 쇼핑몰 프로젝트를 제작한다고 가정하면 회원이

상품을 주문하면 연결 테이블에는 단순히 주문한 회원 아이디와 상품 아이디만 담고 끝나지 않습니다. 보통은

연결 테이블에 주문 수량 컬럼, 주문 날짜 같은 컬럼이 더 필요한 것입니다.

 

 

만약 연결 테이블에 주문 수량과 주문 날짜 컬럼을 추가하면 더이상 @ManyToMany 를 사용할 수 없습니다.

주문 엔티티나 상품 엔티티에는 추가한 컬럼들을 매핑할 수 없기 때문입니다.

이 문제를 해결하려면 연결 테이블와 매핑하는 연결 엔티티를 만들고 이 곳에 추가한 컬럼들을 매핑해야 합니다.

이렇게 되면 엔티티 간의 관계도 테이블 관계처럼 다대다에서 일대다, 다대일 관계로 풀어야 합니다.,

 

@Entity
@Getter @Setter
public class Member{

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

    private String username;

    private int age;

    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProducts;
}

 

@Entity
@Getter @Setter
public class Product {

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

    private String name;   
}

 

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

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

    @Id
    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;
    
    private int orderAmount;
    
    private LocalDateTime orderTime;
}

 

public class MemberProductId implements Serializable {

    private String member;
    private String product;

    @Override
    public boolean equals(Object o) {
        ...
    }

    @Override
    public int hashCode() {
        ...
    }
}

 

상품 엔티티에서는 회원 상품 엔티티로 객체 그래프 탐색 기능이 필요하지 않아 연관관계를 만들지 않았습니다.

회원 상품 엔티티는 연결 테이블과 매핑하는 연결 엔티티입니다. 기본키를 매핑하는 @Id 와 외래키를 매핑하는 

@JoinColumn 을 동시에 사용해서 기본키 + 외래키를 한번에 매핑했습니다. 그리고 @IdClass 를 사용해서 복합키로

매핑했습니다. JPA 에서 복합키를 사용하려면 별도의 식별자 클래스를 생성하고 @IdClass 에 지정하면 됩니다.

 

식별자 클래스 특징

1. 복합키는 별도의 식별자 클래스로 만들어야 합니다

2. Serializable 를 구현해야 합니다.

3. equals() hashcode() 메서드를 구현해야 합니다.

4. 기본 생성자가 있어야 합니다

5. 식별자 클래스는 public 이어야 합니다.

6. @IdClass 를 사용하는 방법 외에 @EmbaddedId 를 사용하는 방법도 있습니다.

 

회원상품은 회원과 상품의 기본키를 받아서 자신의 기본키로 사용합니다. 이렇게 부모 테이블의 기본키를 받아서 자신의

기본키 + 외래키로 사용하는 것을 데이터베이스 용어로 식별 관계라고 합니다. 회원상품은 회원의 기본키를 받아서 

자신의 기본키로 사용함과 동시에 회원관의 관계를 위한 외래키로도 사용합니다. 그리고 상품의 기본키도 받아서

자신의 기본키로 사용함과 동시에 상품과의 관계를 위한 외래키로 사용합니다. 또한 MemberProductId 식별자 클래스로

두 기본키를 묶어서 복합키로 사용합니다.

 

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

복합키는 항상 식별자 클래스를 만들어야 하고 조회할 때도 생성한 식별자 클래스로 엔티티를 조회해야 합니다.

이렇게 되면 사용하는 방법도 복잡하고 ORM 매핑에서도 처리할 일이 정말 많아집니다. 복합키를 사용하지 않고

다대다 관계를 간단하게 구성하는 방법은 데이터베이스에스 자동을 ㅗ생성해주는 대리키를 long 타입 값으로 사용하는

것입니다. 이 방법의 장점은 간편하고 거의 영구히 쓸 수 있으며 비즈니스에 의존하지 않습니다. 그리고 ORM 매핑 시에

복합 키를 만들지 않아도 되므로 간단한 매핑을 완성할 수 있습니다.

 

 

연결 테이블 이름을 ORDER 로 바꾸고 order_id 라는 새로운 기본키를 하나 생성하고 member_id, order_id 컬럼은

외래키로만 사용합니다..

(ORDER 는 MySql 을 포함한 일부 데이터베이스에서 예약어로 잡혀 있어서 테이블 명으로 사용하지 못할 수 있습니다.)

 

@Entity
@Getter @Setter
public class Member{

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

    private String username;

    private int age;

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

 

@Entity
@Getter @Setter
public class Product {

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

    private String name;
}

 

@Entity
@Getter @Setter
public class Order {

    @Id @GeneratedValue
    @Column(name = "order_id")
    long id;

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

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

    private int orderAmount;

    private LocalDateTime orderTime;
}

 

이렇게 대리키를 사용함으로써 이전에 보았던 식별 관계에 복합키를 사용하는 것보다 매핑이 단순하고 이해하기

쉬워졌습니다.

 

다대다 관계를 일대다 다대일 관계로 풀어내기 위해서는

연결 테이블을 만들 때식별자를 어떻게 구성할지 선택해야 합니다.

식별 관계: 받아온 식별자를 기본키 + 외래키로 사용합니다

비식별 관계: 받아온 식별자는 외래키로만 사용하고 새로운 식별자를 추가합니다.

데이터베이스 설계에서는 부모 테이블의 기본키를 받아서 자식 테이블의 기본키 + 외래키로 사용하는 것을 식별 관계라

하며, 단순히 외래키로만 사용하는 것을 비식별 관계라 합니다. 객체 입장에서는 비식별 관계를 사용하는 것이 

복합키를 위한 식별자 클래스를 만들지 않아도 되므로 단순하고 편리하게 ORM 매핑을 할 수 있습니다.

 

이 포스팅은 자바 ORM 표준 JPA 프로그래밍의 내용을 참고하여 작성하였습니다.

댓글