[JPA 프로그래밍] 5. 다양한 연관관계 매핑

반응형
728x90
반응형

다대일 (N:1)

다대일 관계의 반대 방향을 항상 일대다 관계이고, 일대다 관계의 반대 방향은 항상 다대일 관계이다. 외래키는 항상 다(N)쪽에 있으며, 그러므로 연관관계의 주인은 항상 다(N) 쪽이다.

 

- 단방향

MemberA.java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;


@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberA {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String username;

    @ManyToOne
    @JoinColumn(name="TEAM_ID")
    private TeamA teamA;
}

1) MemberA.team 필드를 TEAM_ID 외래 키와 매핑한다.

@ManyToOne
@JoinColumn(name="TEAM_ID")
private TeamA teamA;

 

TeamA.java
import lombok.*;

import javax.persistence.*;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TeamA {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String name;
}

 

실행 쿼리
create table membera (
    id int8 not null,
    name varchar(10) not null,
    team_id int8,
    primary key (id)
)

create table teama (
    id int8 not null,
    name varchar(10) not null,
    primary key (id)
)

 

 

- 양방향

MemberA.java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;


@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberA {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String username;

    @ManyToOne
    @JoinColumn(name="TEAM_ID")
    private TeamA teamA;
    
    /**
     * 양방향 연관관계 set 메서드
     * @param teamA
     */
    public void setTeamA(TeamA teamA) {
        // 기존에 매핑된 팀이 있었다면 매핑 제거
        if (this.teamA != null) {
            this.teamA.getMemberA().remove(this);
        }

        this.teamA = teamA;
        teamA.getMemberA().add(this);
    }
}

1) 양방향 연관관계는 항상 서로를 참조해야한다.

setTeamA() 메서드

public void setTeamA(TeamA teamA) {
    // 기존에 매핑된 팀이 있었다면 매핑 제거
    if (this.teamA != null) {
        this.teamA.getMemberA().remove(this);
    }

    this.teamA = teamA;
    teamA.getMemberA().add(this);
}

 

TeamA.java
import lombok.*;

import javax.persistence.*;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TeamA {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String name;

    @OneToMany(mappedBy = "teamA")
    private List<MemberA> memberA;
    
    public void addMember(MemberA memberA) {
         this.memberA.add(memberA);

         if (memberA.getTeamA() != this) {
             memberA.setTeamA(this);
         }
     }
}

1) 양방향은 외래 키가 있는 쪽이 연관관계의 주인이다.

MEMBER 테이블이 다(N)쪽이므로 외래키를 가지고있다. 따라서 Member.team이 연관관계의 주인이므로 TeamA.java에서 아래와같이 mappedBy 속성을 정의한다.

@OneToMany(mappedBy = "teamA") // team 은 주인이 아님을 설장한다. 주인은 Member.team 이다.
private List<MemberA> memberA;

 

2) 양방향 연관관계는 항상 서로를 참조해야한다.

addMember 메서드

public void addMember(MemberA memberA) {
    this.memberA.add(memberA);

    if (memberA.getTeamA() != this) {
        memberA.setTeamA(this);
    }
}

 

실행 쿼리

 

create table membera (
    id int8 not null,
    name varchar(10) not null,
    team_id int8,
    primary key (id)
)

create table teama (
    id int8 not null,
    name varchar(10) not null,
    primary key (id)
)

 

 

 

일대다(1:N)

일대다 관계는 엔티티를 하나 이상 참조할 수 있으므로 자바 컬렉션인 Collection, List, Set, Map 중 하나를 사용해야한다.

 

- 단방향

TeamB.java
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class TeamB {
    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;
    
    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<MemberB> memberB = new ArrayList<MemberB>();
}

 

MemberB.java
import com.book.jpa.chapter05.Team;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberB {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String username;
}

 

실행 쿼리
create table memberb (
    id int8 not null,
    name varchar(10) not null,
    team_id int8,
    primary key (id)
)

create table teamb (
    team_id int8 not null,
    name varchar(255),
    primary key (team_id)
)

 

일대다 단방향을 매핑할때는 @JoinColumn을 명시해야한다. 그렇지 않으면 JPA는 연결 테이블을 중간에 두고 연관관계를 관리하는 조인 테이블 전략을 기본으로 사용해서 매핑한다.

 

일대다 단방향 매핑의 단점

일대다 단방향 매핑의 단점은 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다는 점이다. 본인 테이블에 외래키가 있으면 엔티티의 저장과 연관관계 처리를 INSERT SQL 한번으로 끝낼 수 있지만 다른 테이블에 외래키가 있으면 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야한다. 

public void testSave() {
    Member member1 = new Member("member1");
    Member member2 = new Member("member2");
    
    Team team1 = new Team("team1");
    team1.getMembers().add(member1);
    team1.getMembers().add(member2);
    
    em.persist(member1); // INSERT-member1
    em.persist(member2); // INSERT-member2
    em.persist(team1); // INSERT-temm1, UPDATE-member1.fk, UPDATE-member2.fk
    
    transaction.commit();
}

 

일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자.

다대일 양방향 매핑은 관리해야하는 외래 키가 본인 테이블에 있기 때문에 일대다 단방향 매핑보다는 다대일 양방향 매핑을 권장한다.

 

- 양방향

일대다 양방향은 존재하지 않는다. 대신 다대일 양방향을 사용해야한다.

@OneToMany는 연관관계의 주인이 될 수 없다. 다(N)쪽에 외래 키가 존재하기 때문에 @OneToMany, @ManyToOne 둘 중에 연관관계 주인은 항상 다 쪽인 @ManyToOne을 사용한 쪽이다. 이런 이유로 @ManyToOne에는 mappedBy 속성이 없다.

 

MemberB.java
import com.book.jpa.chapter05.Team;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberB {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String username;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    private TeamB teamB;
}

1) isnertable = false, updatable = false로 설정

@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
private TeamB teamB;

일대다 단방향 매핑과 같은 TEAM_ID 외래 키 컬럼을 매핑했다. 이렇게되면 둘다 같은 키를 관리하므로 문제가 발생할 수 있다. 따라서 반대편인 다대일 쪽은 isnertable = false, updatable = false로 설정해서 읽기만 가능하도록 한다.

 

실행 쿼리
create table memberb (
    id int8 not null,
    name varchar(10) not null,
    team_id int8,
    primary key (id)
)

create table teamb (
    team_id int8 not null,
    name varchar(255),
    primary key (team_id)
)

 

 

일대일 (1:1)

일대일 관계는 양쪽이 서로 하나의 관계만 가진다. 

 

  • 일대일 관계는 그 반대도 일대일 관계다.
  • 테이블 관계에서 일대다, 다대일은 항상 다(N)쪽이 외래 키를 가진다. 반면에 일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래 키를 가질 수 있다.

 

테이블은 주 테이블이든 대상 테이블이든 외래 키 하나만 있으면 양쪽으로 조회할 수 있다. 따라서 누가 외래키를 가질지 선택해야한다.

 

1) 주 테이블에 외래키

주 객체가 대상 객체를 참조하는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을 참조한다. 외래 키를 객체 참조와 비슷하게 사용할 수 있어서 객체지향 개발자들이 선호한다. 장점은 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있다.

 

- 단방향

MemberC.java
import lombok.*;

import javax.persistence.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberC {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String username;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private LockerC lockerC;
}

 

LockerC.java
import lombok.*;

import javax.persistence.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LockerC {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String name;
}

 

실행 쿼리
create table memberc (
    id int8 not null,
    name varchar(10) not null,
    locker_id int8,
    primary key (id)
)

create table lockerc (
    id int8 not null,
    name varchar(10) not null,
    primary key (id)
)

 

- 양방향

MemberC.java
import lombok.*;

import javax.persistence.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberC {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String username;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private LockerC lockerC;
}

 

LockerC.java
import lombok.*;

import javax.persistence.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LockerC {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String name;
    
    @OneToOne(mappedBy = "lockerC")
    private MemberC memberC;
}

 

실행 쿼리
create table memberc (
    id int8 not null,
    name varchar(10) not null,
    locker_id int8,
    primary key (id)
)

create table lockerc (
    id int8 not null,
    name varchar(10) not null,
    primary key (id)
)

 

 

2) 대상 테이블에 외래키

보통 대상 테이블에 외래키를 두는 것을 선호하는데, 이 방법의 장점은 테이블 관계를 일대일에서 일대다로 변경할때 테이블 구조를 그대로 유지할 수 있기 때문이다. 

 

- 단방향

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

이때는 단방향 관계를 Locker -> Member 방향으로 수정하거나, 양방향 관계로 만들고 Lock를 연관관계의 주인으로 설정해야한다.

 

- 양방향

MemberD.java
import lombok.*;

import javax.persistence.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberD {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String username;

    @OneToOne(mappedBy = "memberD")
    private LockerD lockerD;
}

 

LockerD.java
import com.book.jpa.chapter05.Member;
import lombok.*;

import javax.persistence.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LockerD {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String name;

    @OneToOne
    @JoinColumn(name = "MEMBER_ID")
    private MemberD memberD;
}

주 엔티티인 Member 엔티티 대신에 대상 엔티티인 Locker를 연관관계 주인으로 만들어서 LOCKER 테이블의 외래키를 관리하도록 한다.

 

실행 쿼리
create table memberd (
    id int8 not null,
    name varchar(10) not null,
    primary key (id)
)

create table lockerd (
    id int8 not null,
    name varchar(10) not null,
    member_id int8,
    primary key (id)
)

 

 

다대다 (N:N)

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

 

예시)
회원들은 상품을 주문한다.
상품들은 회원들에 의해 주문된다.

 

이는 연결테이블이 추가로 필요하다. 

https://dodeon.gitbook.io/study/kimyounghan-orm-jpa/06-various-relationship-mapping/n-n

 

그런데 객체는 테이블과 다르게 객체 2개로 다대다 관계를 만들 수 있다. 

 

예시)
회원 객체는 컬렉션을 사용해서 상품들을 참조하면 된다.
상품들도 컬렉션을 사용해서 회원들을 참조하면 된다.

 

https://dodeon.gitbook.io/study/kimyounghan-orm-jpa/06-various-relationship-mapping/n-n

 

 

- 단방향

MemberE.java
import com.book.jpa.chapter06.D일대다.TeamB;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberE {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String username;

    /*
    create table member_product_e (
       member_id int8 not null,
        product_id int8 not null
    )
     */
    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT_E", // 연결 테이블을 지정
               joinColumns = @JoinColumn(name = "MEMBER_ID"), // 현재 방향인 회원과 매핑할 조인 컬럼 정보
               inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID")) // 반대 방향인 상품과 매핑할 조인 컬럼 정보
    private List<ProductE> productEList = new ArrayList<>();
}

1) @JoinTable.name

연결 테이블을 지정한다. 여기서는 MEMBER_PRODUCT_E 테이블로 생성된다.

 

2) @JoinTable.joinColmns

현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정한다. MEMBER_ID로 지정했다.

 

3) @JoinTable.inberseJoinColumns

반대 방향인 상품과 매핑할 조인 컬럼 정보를 지정한다. PRODUCT_ID로 지정했다.

 

ProductE.java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ProductE {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
}

 

실행 쿼리
 create table membere (
    id int8 not null,
    name varchar(10) not null,
    primary key (id)
)

create table member_product_e (
    member_id int8 not null,
    product_id int8 not null
)

create table producte (
    id int8 not null,
    name varchar(255),
    primary key (id)
)

 

다대다 관계를 저장해보자.
@PostMapping("/member/save")
public String saveMember() {
    ProductE productA = new ProductE();
    productA.setId(1L);
    productA.setName("상품A");
    productRepository.save(productA);

    MemberE member1 = new MemberE();
    member1.setId(1L);
    member1.setUsername("회원1");
    member1.getProductEList().add(productA); // 연관관계 설정
    
    memberRepository.save(member1);

    return "ok";
}

/*
    INSERT INTO PRODUCT ...
    INSERT INTO MEMBER ...
    INSERT INTO MEMBER_PRODUCT ...
*/

 

- 양방향

MemberE.java
import com.book.jpa.chapter06.D일대다.TeamB;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberE {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String username;

    /*
    create table member_product_e (
       member_id int8 not null,
        product_id int8 not null
    )
     */
    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT_E", // 연결 테이블을 지정
               joinColumns = @JoinColumn(name = "MEMBER_ID"), // 현재 방향인 회원과 매핑할 조인 컬럼 정보
               inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID")) // 반대 방향인 상품과 매핑할 조인 컬럼 정보
    private List<ProductE> productEList = new ArrayList<>();
    
    // 양방향일때 편의 메소드 추가
    public void addProduct(ProductE productE) {
        productEList.add(productE);
        productE.getMemberEList().add(this);
    }
}

 

ProductE.java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ProductE {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
    
    @ManyToMany(mappedBy = "productEList") // 역방향 추가
    private List<MemberE> memberEList = new ArrayList<>();
}

 

다대다 관계를 저장해보자.
@PostMapping("/member/save")
public String saveMember() {
    ProductE productA = new ProductE();
    productA.setId(1L);
    productA.setName("상품A");
    productRepository.save(productA);

    MemberE member1 = new MemberE();
    member1.setId(1L);
    member1.setUsername("회원1");
//        member1.getProductEList().add(productA); // 연관관계 설정
    member1.addProduct(productA); // 양방향 - 편의 메서드 추가 사용
    memberRepository.save(member1);

    return "ok";
}

 

 

다대다 : 매핑과 한계의 극복

연결 테이블을 사용하기에는 한계가 있다.

https://dodeon.gitbook.io/study/kimyounghan-orm-jpa/06-various-relationship-mapping/n-n

예를 들어 회원이 상품을 주문하면 연결 테이블에 단순히 주문한 회원 아이디와 상품 아이디만 담고 끝나지 않는다. 보통은 추가로 컬럼이 필요해진다. 

이렇게 되면 더는 @ManyToMany를 사용할 수 없다. 추가한 컬럼들은 매핑할 수 없기 때문이다.

 

해결방안
연결 테이블을 매핑하는 연결 엔티티를 만들고 이곳에 추가한 컬럼들을 매핑해야한다. 그리고 엔티티간의 관계도 테이블 관계처럼 다대다에서 일대다, 다대일 관계로 풀어야한다.

 

회원상품(MemberProduct) 엔티티를 추가하자.

 

MemberF.java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberF {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String username;

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

1) 연관관계 주인은 MemberProduct 엔티티다.

mappedBy 속성을 설정해준다.

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

 

ProductF.java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ProductF {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
}

 

MemberProduct.java
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

/**
 * 다대다 에서 컬럼이 추가되어, 별도의 엔티티로 분리
 */
@Entity
@IdClass(MemberProductId.class)
@Setter
@Getter
public class MemberProduct {
    @Id
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private MemberF memberF;

    @Id
    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private ProductF productF;

    /* 컬럼 추가 */
    private int orderAmount;
}

1) 회원상품 식별자 클래스를 설정한다.

@IdClass(MemberProductId.class)

 

 

회원상품 식별자 클래스를 생성하자.

MemberProductId.java
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

@EqualsAndHashCode
@Getter
@Setter
public class MemberProductId implements Serializable {
    private Long memberF;
    private Long productF;
}

복합 기본 키

회원상품 엔티티는 기본키가 MEMBER_ID, PRODUCT_ID로 이루어진 복합 기본 키다.

JPA에서 복합 키를 사용하려면 별도의 식별자 클래스를 만들어야한다. 그리고 엔티티에 @IdClass를 사용해서 식별자 클래스를 지정한다. (위 MemberProduct.java 에 설정되어있다.)

 

특징
  • 복합 키는 별도의 식별자 클래스로 만들어야한다.
  • Serializable을 구현해야한다.
  • equals와 hashCode 메소드를 구현해야한다.
  • 기본 생성자가 있어야한다.
  • 식별자 클래스는 public 이여야한다.
  • @IdClass를 사용하는 방법 외에 @EmbeddedId를 사용하는 방법도 있다.

 

식별 관계

회원상품은 회원과 상품의 기본키를 받아서 자신의 기본키로 사용한다. 이렇게 부모 테이블의 기본 키를 받아서 자신의 기본 키 + 외래 키로 사용하는 것을 데이터베이스 용어로 식별 관계라고 한다.

 

 

저장하는 코드를 생성해보자.
@PostMapping("/test")
public String saveMember() {
    // 회원저장
    MemberF memberF = new MemberF();
    memberF.setId(1L);
    memberF.setUsername("회원1");
    memberRepository.save(memberF);

    // 상품저장
    ProductF productF = new ProductF();
    productF.setId(1L);
    productF.setName("상품1");
    productRepository.save(productF);

    // 회원상품 저장
    MemberProduct memberProduct = new MemberProduct();
    memberProduct.setMemberF(memberF); // 주문회원 - 연관관계 설정 
    memberProduct.setProductF(productF); // 주문상품 - 연관관계 설정
    memberProduct.setOrderAmount(2); // 주문수량
    
    memberProductRepository.save(memberProduct);

    return "ok";
}

복합 키를 사용하는 방법은 복잡하다. 단순히 컬럼 하나만 기본키로 사용하는 것과 비교해서 복합 키를 사용하면 ORM 매핑에서 처리해야할 일이 상당히 많아진다. 또한 식별자 클래스도 만들어야하고, @IdClass 또는 @EmbeddedId도 사용해야한다. 

복합 키를 사용하지 않고 간단한 다대다 관계를 구성하는 방법을 알아보자.

 

 

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

추천하는 기본 키 생성 전략

데이터베이스에서 자동으로 생성해주는 대리 키를 Long 값으로 사용하는 것이다. 이번에는 연결 테이블에 새로운 기본키를 사용해보자. 회원상품(MemberProduct) 보다는 주문(Order)이라는 이름이 더 어울릴 것이다.

 

https://dodeon.gitbook.io/study/kimyounghan-orm-jpa/06-various-relationship-mapping/n-n

 

Orders.java
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

/**
 * MemberProduct 를 Order 엔티티로 새로 생성하는게 편할 수도 있다.
 */
@Entity
@Setter
@Getter
public class Orders {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private MemberG memberG;

    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private ProductG productG;

    /* 컬럼 추가 */
    private int orderAmount;
}

 

MemberG.java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberG {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME", nullable = false, length = 10)
    private String username;

    @OneToMany(mappedBy = "memberG")
    private List<Orders> orders = new ArrayList<>();
}

 

ProductG.java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ProductG {
    @Id
    @Column(name= "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
}

 

실행 쿼리
create table memberg (
    id int8 not null,
    name varchar(10) not null,
    primary key (id)
)

create table productg (
    id int8 not null,
    name varchar(255),
    primary key (id)
)

create table orders (
    id int8 not null,
    order_amount int4 not null,
    member_id int8,
    product_id int8,
    primary key (id)
)

 

 

다대다 연관관계 정리

  • 식별 관계 : 받아온 식별자를 기본키 + 외래 키로 사용한다.
  • 비식별 관계 : 받아온 식별자는 외래 키로만 사용하고 새로운 식별자를 추가한다.

 

 

반응형

Designed by JB FACTORY