[JPA] 5장 연관관계 매핑 기초

diveintoo·2022년 2월 19일
0

본 글은 김영한님의 <자바 ORM 표준 JPA 프로그래밍>을 읽고 공부한 내용을 정리한 글입니다.

  1. 단방향 연관관계
  2. 연관관계 사용
  3. 양방향 연관관계
  4. 연관관계의 주인
  5. 양방향 연관관계 저장
  6. 양방향 연관관계의 주의점

1. 단방향 연관관계

객체는 참조(주소)로 연관관계를 맺고, 테이블은 외래 키로 연관관계를 맺습니다.
테이블은 외래 키 하나로 양방향 조인이 가능하지만, 참조를 통한 연관관계는 언제나 단방향입니다.
따라서 양방향 관계를 만들고 싶다면 서로 다른 단방향 관계 2개를 만들어야합니다.

객체 관계 매핑


객체의 Member.team과 테이블의 MEMBER.TEAM_ID를 매핑하는 것이 연관관계 매핑입니다.

@Entity
public class Member {
	...
    
	@ManyToOne
    @JoinColumn(name="TEAM_ID)
    private Team team;
    
    ...
}

@Entity
public class Team {
	@Id
    @Column(name="TEAM_ID")
    private String id;

}

@ManyToOne : 다대일 연관관계를 나타냅니다. 생략 불가!
@JoinColumn(name="TEAM_ID) : 외래 키를 매핑합니다. 생략시 {필드명 + _ + 참조 테이블의 컬럼명} 외래 키를 사용합니다.

2. 연관관계 사용

2.1 저장

Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); // 연관관계 설정 member1 -> team1
em.persist(member1);

JPA는 참조한 팀의 식별자(Id)를 외래 키로 사용해서 적절한 등록 쿼리를 생성합니다.

❗️JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 합니다.

2.2 조회

객체 그래프 탐색

Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); // 객체 그래프 탐색

이처럼 객체를 통해 연관된 엔티티를 조회하는 것이 객체 그래프 탐색이다.

JPQL 사용

select m from Member m join m.team t
where t.name=:teamName

조인 JPQL은 위와 같습니다. :teamName과 같이 :로 시작하는 것은 파라미터를 바인딩받는 문법입니다.

2.3 수정

Member member = em.find(Member.class, "member1");
member.setTeam(team2); // 회원1의 팀 수정(team1 -> team2)

이렇게 엔티티의 값만 변경해두면 변경 감지 기능으로 인해 자동으로 데이터 베이스에 반영됩니다.

2.4 제거

Member member = em.find(Member.class, "member1");
member.setTeam(nul); // 연관관계 제거

연관관계를 null로 설정하면 연관관계가 삭제됩니다.

2.5 연관된 엔티티 삭제

member.setTeam(null); // 회원 연관관계 제거
em.remove(team); // 팀 삭제

연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야 합니다.

3. 양방향 연관관계


이번에는 팀에서도 회원으로 접근할 수 있도록 양방향 연관관계를 매핑해보겠습니다.

팀에서 회원은 일대다 관계이기 때문에 여러 건과 연관관계를 맺을 수 있도록 List 컬렉션을 사용할 것입니다.
데이터베이스 테이블은 외래 키 하나로 양방향으로 조회할 수 있기 때문에 추가할 사항이 없습니다.

양방향 연관관계 매핑

@Entity
public class Member {
	...
    
	@ManyToOne
    @JoinColumn(name="TEAM_ID)
    private Team team;
    
    ...
}

@Entity
public class Team {
	@Id
    @Column(name="TEAM_ID")
    private String id;
    
    @OneToMany(mappedBy="team") // 일대다 연관관계
    private List<Member> members = new ArrayList<Member>();

}

@OneToMany(mappedBy="team") : 일대다 관계를 매핑합니다. mappedBy 속성에는 반대쪽 매핑의 필드 이름 값을 줍니다. 이 경우에는 Member.team이므로 team을 값으로 주었습니다.

4. 연관관계의 주인

테이블은 외래 키 하나로 두 테이블의 연관관계를 관리합니다.
엔티티를 양방향으로 매핑하면 두 곳에서 서로를 참조하기 때문에 객체의 참조는 둘인데 외래 키는 하나인 상황이 발생합니다.
이 때 두 객체 연관관계 중 하나를 정해서 테이블의 외래 키를 관리하는 것을 연관관계의 주인이라고 합니다.

연관관계의 주인은 외래 키가 있는 곳

연관관계의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리할 수 있습니다.
반면에 주인이 아닌 쪽은 읽기만 할 수 있습니다.

  • 주인은 mappedBy 속성을 사용하지 않습니다.
  • 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야합니다.

연관관계의 주인을 정한다는 것은 외래 키 관리자는 선택하는 것이기 때문에 외래 키를 가진 Member.team을 주인으로 정해야 한다.

5. 양방향 연관관계 저장

Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); // 연관관계 설정 member1 -> team1
em.persist(member1);

team1.getMembers().add(member1); 과 같은 코드가 있어야할 것 같지만 Team.members는 연관관계의 주인이 아니기 때문에 무시됩니다.

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

객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전합니다.

Member member1 = new Member("member1", "회원1");

// 양방향 연관관계 설정
member1.setTeam(team1); // 연관관계의 설정 member1 -> team1
team1.getMembers().add(member1); // 연관관계의 설정 team1 -> member1
em.persist(member1);

Member.team : 연관관계의 주인이며 이 값으로 외래 키를 관리합니다.
Team.members : 주인이 아니므로 저장 시에 사용되지 않습니다.

객체의 양방향 연관관계는 양쪽 모두 관계를 맺어줍시다!

연관관계 편의 메소드

양방향을 연결하는 코드를 리팩토링해보겠습니다.

@Entity
public class Member {
	...
    
	public Team;
    
    public void setTeam(Team team) {
		if (this.team != null) {
        	this.team.getMembers().remove(this);
        }
        this.team = team;
        team.getMembers().add(this);
    }
    
    ...
}

0개의 댓글