본문 바로가기
JPA/JPA

[JPA] 연관관계 매핑 - 1

by 걸어가는 신사 2022. 1. 1.
  • 객체와 테이블 연관관계의 차이를 이해
  • 객체의 참조테이블의 외래 키를 매핑
  • 엔티티의 연관관계를 매핑할 때 다음 3가지를 고려해야 한다.
    • 다중성
    • 단방향, 양방향
    • 연관관계의 주인

1. 단방향 연관관계

다대일 연관관계, 단방향

  • 객체 연관관계
    • 회원 객체는 Member.team 멤버 변수로 팀 객체와 연관관계를 맺는다.
    • 회원 객체와 팀 객체는 단방향 관계다.
    • Member.team 필드를 통해서 팀을 알 수 있지만 반대로 팀은 회원을 알 수 없다.
    • member.getTeam() 가능, team.getMember()는 불가능
  • 테이블 연관관계
    • 회원 테이블은 TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺는다.
    • 회원 테이블과 팀 테이블은 양방향 관계이다.
    • 회원 테이블의 TEAM_ID 외래 키를 통해서 회원과 팀을 조인할 수 있고 반대로 팀과 회원도 조인할 수 있다.
  • 객체 연관관계와 테이블 연관관계의 가장 큰 차이
    • 참조를 통한 연관관계는 언제나 단뱡향이다. 
    • 양쪽에서 서로 참조하는 것을 양방향 연관관계라 한다.
      • 정확히 이야기하면 양방향 관계가 아니라 서로 다른 단방향 관계 2개다.

(1) 객체 관계 매핑

@Entity
@Getter @Setter
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String name;
    
    @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;
}
  • @ManyToOne
    • 다대일(N:1) 관계라는 매핑 정보
    • 연관관계를 매핑할 때 이렇게 다중성을 나타내는 어노테이션은 필수이다.
속성 기능  기본값
optional false로 설정하면 연관된 연관된 엔티티가 항상 있어야 한다. true
fetch 글로벌 페치 전략을 설정한다. @ManyToOne=FetchType.EAGER
@OneToMany=FetchType.LAZY
cascade 영속성 전이 기능을 사용한다.  
targetEntity 연관된 엔티티의 타입 정보를 설정한다.
이 기능은 거의 사용하지 않는다.
컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다.
ex)
@OneToMany
private List<Member> members;
@OneToMany(targetEntity=Member.class)
private List members;
 
  • @JoinColumn
    • 외래 키를 매핑할 때 사용한다.
    • name 속성에는 매핑할 외래 키 이름을 지정한다.
속성 기능 기본값
name 매핑할 외래 키 이름 필드명 +_+ 참조하는 테이블의 기본 키 컬럼명
ex) 필드명(team),
참조하는 데이블의 컬럼명(TEAM_ID)
=> team_TEAM_ID
referencedColumnName 외래 키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본키 컬럼명
foriegnKey(DDL) 외래 키 제약조건을 직접 지정할 수 있다.
이 속성은 테이블을 생성할 때만 사용한다.
 
unique
nullable
insertable
updateable
columnDefinition
table
@Column의 속성과 같다.  

(2) 연관관계 등록, 조회

//단방향 연관관계
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);

em.flush(); //insert 쿼리가 나간다.
em.clear(); 
//영속성 context를 초기화 해주었기 때문에 뒤에 나오는 em.find() 메소드를 통해 select 쿼리가 나간다.

Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam(); //객체 그래프 탐색
System.out.println(findTeam.getName());

 

2. 양방향 연관관계

양방향 연관관계

  • 객체 연관관계
    • 팀에서 회원은 일대다 관계이다.
    • 일대다 관계는 여러 건과 연관관계를 맺을 수 있으므로 컬렉션을 사용해야 한다.
    • List 컬렉션 추가
  • 데이블 관계
    • 데이터베이스 테이블은 외래 키 하나의 양방향으로 조회할 수 있다.
    • 데이터베이스에 추가할 내용은 전혀 없다.

(1) 객체 관계 매핑

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

    private String name;

    @OneToMany(mappedBy = "team") //Member class의 team 변수와 연관관계 매핑되어있다.
    private List<Member> members = new ArrayList<>();
}
  • Member Entity는 변경한 부분이 없다.
  • Team Entity에는 List<Member> members를 추가
  • @OneToMany 매핑 정보 사용
  • mappedBy 속성은 양방향 매핑일 때 사용하는데 반대쪽 매핑의 필드 이름을 값으로 주면 된다.

(2) 연관관계 등록, 조회

//양방향 연관관계
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);

em.flush();
em.clear();

Member findMember = em.find(Member.class, member.getId());
List<Member> members = findMember.getTeam().getMembers(); //객체 그래프 탐색
for (Member m : members) {
	System.out.println(m.getName());
}
  • 회원 컬렉션으로 객체 그래프 탐색을 사용할 수 있다.

 

3. 연관관계 주인

(1) 연관관계 주인의 필요성

  • 단순히 @OneToMany만 있으면 되지 mappedBy는 왜 필요할까?
  • 객체 연관관계 : 2개
    • 회원 -> 팀 연관관계 1개 (단방향)
    • 팀 -> 회원 연관관계 1개 (단방향)
  • 테이블 연관관계 = 1개
    • 회원 <-> 팀의 연관관계 1개
  • 엔티티를 양방향 연관관계로 설정하면 객체의 참조는 둘인데 외래 키는 하나다. 둘 사이에 차이가 발생한다.
  • 두 객체 연관관계 중 하나를 정해서 테이블의 외래 키를 관리해야 하는데 이것을 연관관계의 주인이라 한다.

(2) 연관관계 주인

  • 두 연관관계 중 하나를 연관관계의 주인으로 정해야 한다.
  • 연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있다.
  • 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야 한다.

(3) 연관관계 주인 정하기

  • 연관관계의 주인을 정한다는 것은 사실 외래 키 관리자를 선택하는 것이다.
    • 만약 회원 엔티티에 있는 Member.team을 주인으로 선택하면 자기 테이블에 있는 외래 키를 관리한다.
    • 하지만 팀 엔티티에 있는 Team.members를 주인으로 선택하면 물리적으로 전혀 다른 테이블의 외래 키를 관리해야 한다.
연관관계의 주인은 외래 키가 있는 곳으로 정하자!

연관관계의 주인

  • 연관관계의 주인만 데이터베이스 연관관계와 매핑되고 외래 키를 관리할 수 있다.
  • 주인이 아닌 반대편은 읽기만 가능하고 외래 키를 변경하지 못한다.

 

4. 양방향 연관관계의 주의점

(1) 역방향(주인이 아닌 방향)만 값을 입력

// 양방향 연관관계와 연관관계 주인 - 주의
Member member = new Member();
member.setName("member1");
em.persist(member);

Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member); //읽기 전용이다. 값을 넣어줘도 DB에 반영되지 않는다.
em.persist(team);
  • 데이버베이스에서 MEMBER 테이블을 조회하게 되면 TEAM_ID가 null 값이 입력되어 있다.
  • 연관 관계의 주인이 아닌 쪽에서 값을 수정해도 DB에 반영되지 않는다.
  • 연관관계의 주인만이 외래 키의 값을 변경할 수 있다!

(2) 순수한 객체까지 고려한 양방향 연관관계

  • 연관관계의 주인에만 값을 저장하고 주인이 아닌 곳에는 값을 저장하지 않아도 될까?
  • 객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전한다.
// 양방향 연관관계와 연관관계 주인
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");

member.setTeam(team); //연관관계의 주인
team.getMembers().add(member); //주인이 아니다. 저장 시 사용되지 않는다.
em.persist(member);
  • Test 케이스 작성 시에 JPA를 이용하지 않고 Test를 하는 경우 필요
  • 객체까지 고려해서 아닌 곳에도 값을 입력하는 것이 좋다.
  • 객체의 양방향 연관관계는 양쪽 모두 관계를 맺어주자.

(3) 연관관계 편의 메소드

  • 양방향 연관관계는 양쪽 다 신경 써야 한다.
  • 호출하다 보면 실수로 둘 중 하나만 호출해서 양방향이 깨질 수 있다.
  • 양방향 관계에서 두 코드는 하나인 것처럼 사용하는 것이 안전하다.
@Entity
@Getter @Setter
public class Member {
	...
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }
}
  • changeTeam 메소드 하나로 양방향 관계를 모두 설정하도록 변경하였다.
  • 이렇게 한 번에 양방향 관계를 설정하는 메소드를 연관관계 편의 메소드라 한다.

 

5. 정리

  • 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 이미 완료되었다.
    • 우선 단방향 매핑만 하고 양방향은 필요할 때 추가해도 된다. (테이블에 영향을 주지 않는다.)
    • 객체 그래프 탐색 기능(JPQL 쿼리 탐색)이 필요할 때 양방향 기능을 추가하자.
  • 연관관계의 주인은 항상 외래 키가 있는 곳으로 정하자!
  • 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리해야 한다.

 

반응형

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

[JPA] 상속 관계 매핑  (0) 2022.01.03
[JPA] 연관관계 매핑 - 2  (0) 2022.01.02
[JPA] 엔티티 매핑 (Entity Mapping)  (0) 2021.12.31
[JPA] 영속성 컨텍스트 (Persistence context)  (0) 2021.12.29
JPA 사용하기 (feat. Maven)  (0) 2021.12.28

댓글