- 용어 이해
- 방향 (Direction) : 단방향, 양방향
- 다중성(Muitplicity) : 다대일 (N : 1), 일대다 (1 : N), 일대일 (1 : 1), 다대다 ( N : M)
- 연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리가 필요
💡 연관관계의 필요성
객체지향 설계의 목표는 자율적인 객체들의 협력 공통체를 만드는 것인데 객체를 테이블에 맞춰 데이터 중심으로 모델링을 하면
협력 관계를 만들 수 없기 때문이다.
예시와 함께 살펴보자.
객체를 테이블에 맞추어 모델링 할 경우 (연관관계가 없는 객체)

- 객체를 테이블에 맞추어 모델링한 코드
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@Column(name = "TEAM_ID")
private Long teamId;
...
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
...
}
- 모델링 한 객체를 가지고 DB에 저장 & 조회 하는 로직
//팀 저장
Team team = new Team();
team.setName("tistory");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("jisoolog");
member.setTeamId(team.getId());
em.persist(member);
- 이때 회원의 팀을 찾고싶다면 어떻게 해야 할까?
Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getId();
Team findTeam = em.find(Team.class, findTeamId);
- 연관관계가 존재하지 않기 때문에, member 의 teatm을 가져와야 할 경우
- member 를 꺼내온 뒤, id 를 찾아 team 을 다시 조회해야 하기 때문에
- 회원의 team 을 가져오기 위해 많은 비용이 든다.
- 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾고
- 객체는 객체 참조를 사용하여 연관된 객체를 찾기 때문에
- 패러다임의 불일치가 발생한다.
- 정리해보자면
- 객체를 테이블에 맞추어 데이터 중심으로 모델링할 경우에는 협력관계를 만들 수가 없고
- 객체 지향스럽지 않은 코드를 작성하게 된다.
💡 단방향 연관관계

@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
...
}
- 회원(Member) 객체는 @ManyToOne, @JoinColumn 을 통해서 팀(Team) 을 참조하도록 설정해주었다.
- 회원(Member) 객체와 팀(Team)객체는 단방향 관계이기 때문에
- 회원(Member) 는 team 필드를 통해 팀을 알 수 있지만, 팀(Team) 객체는 소속된 member 에 대해서 알 수 없다.
- 회원(Member) 객체와 팀(Team)객체는 단방향 관계이기 때문에
- 테이블의 연관관계는 외래키 하나를 가지고 양측에서 서로 참조할 수 있다.

💡 양방향 연관관계

- 양방향으로 연관관계를 맺어주면, 양측에서 서로를 참조할 수 있게 된다.
- 단방향에서는 팀 객체에서는 소속된 member에 대해서 조회할 수 없었는데 양방향을 설정해주면 조회가 가능하게 된다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
...
}
❗️ 하지만 객체와 테이블이 관계를 맺을때의 차이점이 존재한다.
- 객체 연관관계 = 2개
- 회원 -> 팀 (단방향)
- 팀 -> 회원 (단방향)
- 테이블 연관관계 = 1개
- 회원 <-> 팀
때문에 객체에서 양방향 연관관계를 맺을 경우 둘 중 하나를 외래키로 관리해야 한다.

- 두 객체가 서로를 참조하도록 값을 만들어 두었기 때문에 테이블에서 어떤 것을 외래키로 만들어 관리해야 할지 정해주어야 한다.
- 연관관계 주인을 정해야 한다.
💡 연관관계의 주인
- 양방향 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리(등록, 수정)
- 주인이 아닌쪽은 읽기만 가능하다.
- 주인은 mappedBy 속성을 사용하지 않는다.
- 주인이 아니면 mappedBy 속성으로 주인을 지정한다.
❓ 이때 누구를 주인으로 정해주어야 할까?
- 외래키가 있는 쪽을 주인으로 정해야 한다.
- DB입장에서 외래키가 있는 쪽이 N(다) 이고 없는 쪽이 1이기 때문에
- N 쪽이 연관관계의 주인이 되는 것.
❗️양방향 매핑시 가장 많이 하는 실수
연관관계의 주인에 값을 입력하지 않는 경우

- 실행시켜보면
-
- 연관관계의 주인에 값을 입력하지 않을 경우 null 값이 들어오는 것을 볼 수 있다.
- 이때 TEAM_ID 에 null 값이 들어오는 이유는 team 의 member 의 경우 주인이 아니기 때문에
- 읽기 전용이라 변경된 내용이 반영되지 않는다.

- 코드를 변경하여 team이 아닌 member 에서 team 을 설정해주고

- 실행시켜 주면
- 잘 반영되는 것을 확인할 수 있다.

정리해보면 🤔
- 양방향 매핑 시 연관관계의 주인에 값을 입력해야 한다.
- 또한 순수한 객체 관계를 고려하면 양쪽 다 값을 입력해야 한다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setName("mamber1");
team.getMembers().add(member);
member.setTeam(team);
em.persist(member);
- 이때 team.getMembers().add(member) 를 넣지 않을 경우 문제가 발생한다.
- DB에 반영하는데 문제는 생기지 않지만
- 영속화 컨텍스트의 1차 캐시에 저장된 team 에서는 members 에 해당 Member가 추가되지 않은 상태이다.
- 이때, team.members 를 사용하게 된다면
- DB에서 조회하는 것이 아닌 1차 캐시에서 꺼내 사용하기 때문에 해당 member 가 추가되지 않은 결과가 반환되어 문제가 발생하기 때문에 양쪽 모두에 값을 세팅해주어야 한다.
- 연관관계 편의 메서드를 생성하여 사용하는 것이 좋다.
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
- Team 을 세팅해주는 시점에 해당 team에 Member 도 같이 추가가 된다.
양방향 매핑 정리
- 단방향 매핑을 우선으로 한 후 양방향은 필요할 때 추가해주자. (테이블에는 영향을 주지 않는다.)
<자바 ORM 표준 JPA 프로그래밍 - 기본 편 을 참고하여 작성하였습니다.>
'JPA' 카테고리의 다른 글
| [Jpa] 다대다 [N:N] 에 대해서 (0) | 2023.05.17 |
|---|---|
| [Jpa] 다대일[N:1], 일대다[1:N] 에 대해서 (0) | 2023.05.09 |
| [Jpa] 엔티티 매핑 (0) | 2023.04.30 |
| [Jpa] 영속성 컨텍스트 (0) | 2023.04.27 |
| [Jpa] @OneTOOne에 대해서 (0) | 2023.04.26 |