본문 바로가기

Spring/JPA

JPA- 연관관계 매핑

단방향 연관관계

 - 외래키 연관관계

 지금 MEMBER와 TEAM은 일대다 연관관계이다. 테이블 연관관계를 보면 외래키(FK)는 일대다 중 에 들어가는 것을 알 수 있다.

 각 MEMBER가 자신이 어느 팀인지를 아는 것(TEAM의 외래키를 갖는 것)이 TEAM이 모든 MEMBER들의 키를 갖는 것보다 이치에 맞기 때문이다. 하지만 이런식으로 코드를 짜는 경우 객체 지향적인 설계를 하지 못한다는 단점이 존재한다. 

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            //Team 저장
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            //Member 저장
            Member member = new Member();
            member.setUserName("MemberA");
            member.setTeamId(team.getId());  //Team의 fk를 직접 가져와서 주입
            em.persist(member);
            
            //Member에 연관된 Team 조회
            Member findMember = em.find(Member.class, member.getId());
            Team findTeam = em.find(Team.class, findMember.getTeamId());
            
            tx.commit();
        } catch(Exception e) {
            tx.rollback(); //오류 발생 시 롤백
        } finally {
            //종료
            em.close();
        }
        emf.close();
    }
}

 위 코드를 보면 저장된 Team 객체를 꺼내오기 위해서 Member에서 teamId(fk)를 조회한 후 teamId로 또 다시 em.find 해야한다. 이는 Member가 직접 Team을 참조하는 것이 아닌 외래키만 가지고 있기 때문이다. 만일 Member가 직접 Team을 참조하는 경우 굳이 이렇게 번거롭게 조회하는 것이 아니라 member.getTeam() 만 하면 될 것이다.

 - 직접 참조를 통한 연관관계

package hellojpa;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Getter @Setter
public class Team {

    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
}
package hellojpa;
import lombok.Getter;
import lombok.Setter;

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

@Entity
@Getter @Setter
public class Member {

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

    @Column(name = "name", nullable = false)
    private String userName;

    @ManyToOne  //member:team = N:1
    @JoinColumn(name = "TEAM_ID")  //Join할 "외래 키" 칼럼 명
    private Team team;

    public Member() {}

}

 기존에는 Member안에 Team 객체가 따로있는 것이 아닌 Long teamId가 존재해서 teamId로 매핑을 했었다. 하지만 @ManyToOne, @JoinColumn 어노테이션으로 외래키가 아닌 직접 참조를 통해서 연관관계를 매핑할 수 도 있다.

 

@ManyToOne

 연관관계를 보면 Member와 Team은 다대일 관계이다. 즉슨 하나의 팀에 여러 멤버가 연결되어있다는 것이다. 따라서 Member의 team의 경우 다대일 == ManyToOne이다. 따라서 @ManyToOne을 사용한다.

 

@JoinColumn

 @JoinColumn의 name 속성에는 매핑할 "외래 키" 칼럼명을 명시한다. 우리는 Team 테이블의 TEAM_ID를 가지고 매핑을 할 것이므로 name = "TEAM_ID"를 사용한다.

 

양방향 연관관계

 위의 단방향 매핑에서 객체의 경우 Member에서 Team으로 갈 수는 있어도(member.getTeam()) Team에서 Member로 갈수는 없었다. 즉 단방향 관계이다.

 하지만 테이블은 객체와 별개로 MEMBER에서 TEAM의 PK와 FK를 JOIN함으로써 자신이 어느 팀에 속해있는지 알 수 있고, 반대로 TEAM에서도 PK에 MEMBER의 FK를 JOIN하면 우리팀에 어떤 멤버가 있는지 알 수 있다. 결국 테이블 연관관계에서는 FK하나로 양방향 관계가 만족된다.

 

 결국 객체의 참조와 테이블의 외래키라는 패러다임의 차이로 인해서 단방향 양방향의 차이가 존재하는 것이다.

 그러나 Team에 member들의 List를 넣어준다면 Team에서도 얼마든지 member들을 조회할 수 있을 것이다. 그럼으로써 양방향 관계가 충족된다.

package hellojpa;

import lombok.Getter;
import lombok.Setter;

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

@Entity
@Getter @Setter
public class Team {

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

    //Team:Member = 1:N  일대다관계
    @OneToMany(mappedBy = "team")  // Member.team이 연관관계 주인. members로는 읽기만 가능. 
    private List<Member> members = new ArrayList<>();
}

@OneToMany

 team과 members의 관계는 1:N, 일대다 관계이므로 @OneToMany로 매핑한다. 

 

객체의 양방향 관계

 - 테이블

team_id(외래키)만으로 양쪽에서 모두 확인 가능

  엄밀히 말하자면 테이블은 방향이 없다. 그냥 외래키 하나만으로 양 테이블의 연관관계를 관리한다.

 - 객체

  객체는 사실 List를 통해서 양방향을 충족한다고 했지만 사실 양방향이 아니라 단방향이 두개인 것이다.

  1. Member -> Team (단방향)
  2. Team -> Member (단방향)

객체의 양방향 관계 == 2개의 단방향 관계

사실 위와 같이 단방향 두개로 구성되어 있다. 다시 말해 객체를 양방향으로 참조하기 위해선 단방향 연관관계를  2개 만들어야 하는 것이다.

 

연관관계 주인

 외래키를 관리하는 것을 보면 Team의 members에도 외래키가 있고 Member에도 외래키가 존재한다. 즉 외래키 관리를 양쪽에서 다 가능한 것이다. 근데 이러면 나중에 아주 복잡해질 수 있으니 외래키의 관리를 한쪽에서만 하도록 한다. 

이렇게 외래키를 관리하는 쪽을 연관관계 주인이라고 한다.

중요!!!) 외래키가 있는 곳을 연관관계 주인으로 정하라

항상 일대다에서 "다"에 외래키가 있다. 결국 Member가 외래키를 갖고 Member.team이 연관관계 주인이 된다!!!! 

package hellojpa;

import lombok.Getter;
import lombok.Setter;

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

@Entity
@Getter
@Setter
public class Team {

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

    // Member와 Team의 일대다 관계 중 Member가 多.
    // 多인 쪽에 외래키가 있다.
    // 따라서 Member.team이 연관관계 주인이 된다.
    // Team:Member = 1:N  일대다관계 == OneToMany
    @OneToMany(mappedBy = "team")  // members로는 조회만 가능. Member.team이 연관관계 주인
    private List<Member> members = new ArrayList<>();

'Spring > JPA' 카테고리의 다른 글

JPA- 다대일, 일대일  (0) 2023.03.08
JPA- 연관관계 매핑 주의점  (0) 2023.03.05
JPA- 중요! 엔티티 매핑  (0) 2023.02.28
JPA- flush와 영속성 관리  (0) 2023.02.28
JPA- 영속성 컨텍스트  (0) 2023.02.28