연관관계가 필요한 이유
아래와 같은 시나리오를 가정해보자.
- 회원과 팀이 있다.
- 회원은 하나의 팀에만 소속될 수 있다.
- 회원과 팀은 다대일(N:1) 관계다.
이 시나리오대로 테이블을 모델링하면 아래와 같이 모델링이 된다.
그렇다면 이 시나리오를 테이블에 맞춰 Entity객체로 변경하면 어떻게 될까?
보이는 것과 같이, Member 객체에 teamId를 추가해 소속 팀을 입력하고있다. 이렇게 객체가 설계된다면 새 사용자를 만들때 코드는 아래와 같이 외래키를 직접 다루게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
|
// 새 팀 생성 및 저장
Team team = new Team();
team.setName("팀이름");
entityManager.persist(team);
// 새 사용자 생성 및 저장
Member member = new Member();
member.setName("사용자");
member.setTeamId(team.getId()); // 외래키 식별자를 직접
entityManager.persist(member);
|
cs |
추가로, 사용자를 조회해서 조회된 사용자의 소속 팀의 이름을 가져오려면 어떻게 해야할까?
1
2
3
4
5
|
Member findMember = entityManager.find(Member.class, member.getId());
Team findTeam = entityManager.find(Team.class, findMember.getTeamId());
System.out.println(findTeam.getName());
|
cs |
전혀 객체지향적이지 않은 코드가 나오게 된다. 왜 이렇게 된걸까?
객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
- 테이블 : 외래키로 조인을 이용해 연관된 테이블을 찾는다.
- 객체 : 참조를 사용해서 연관된 객체를 찾는다.
때문에, 위에서 설계한 내용은 객체를 객체답게 사용하지 못할 수 밖에 없는 설계인 것이다.
그렇다면 어떻게 설계해야 객체를 객체답게 사용할 수 있을까?
단방향 연관관계
간단하다. 앞에서 설계한 객체 모델링에서 Member 클래스의 teamId를 Team 클래스를 바라보도록 수정하면 된다.
이 설계에서 Member 클래스의 내용은 어떻게 작성될까?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Entity
public class Member
{
@Id
@GeneratedValue
private Long id;
private String name;
private int age;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
// getters, setters
}
|
cs |
@ManyToOne 어노테이션은 Member-Team 간의 다대일 관계를 의미하며, @JoinColumn은 MEMBER 테이블에서 TEAM 테이블에 조인할 컬럼명을 지정한다.
앞서 팀과 사용자를 신규 생성해 저장하는 코드를 변경된 설계에 맞춰 수정해보자.
1
2
3
4
5
6
7
8
9
10
|
Team team = new Team();
team.setName("팀이름");
entityManager.persist(team);
Member member = new Member();
member.setName("사용자");
member.setTeam(team); // 참조
entityManager.persist(member);
|
cs |
수정된 코드에서는 Member에 Team 객체를 직접 set해주어 참조 저장을 하고있다. 언뜻 보기에는 별 차이 없어보이지만, 사용자의 소속 팀 이름을 조회하는 코드를 수정해보면 차이를 느낄 수 있을것이다.
1
2
3
4
5
|
Member findMember = entityManager.find(Member.class, member.getId());
Team findTeam = findMember.getTeam(); // 참조로 연관관계를 조회한다!
System.out.println(findTeam.getName());
|
cs |
Member에서 팀 아이디를 가져와 다시 조회를 하지 않고, Member에 있는 Team 객체를 통해 객체 그래프 탐색으로 팀 이름을 가져올 수 있다!
양방향 연관관계
사용자(Member)의 소속팀(Team)을 찾는 것은 해봤고, 반대로 특정 Team 소속의 사용자들을 찾고싶을 때는 어떻게 해야할까?
Team 클래스에 List<Member> 속성을 추가해 양방향 매핑을 해주면 된다.
이렇게 양방향 매핑으로 설계되었을 때, Team 클래스는 아래와 같이 작성된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Entity
public class Team
{
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
// getters, setters
}
|
cs |
@OneToMany 어노테이션은 Team-Member 간 일대다 관계를 의미하며, mappedBy는 Member 클래스의 team 변수를 통해 관계를 매핑하겠다는 의미이다.
양방향 매핑을 했으니, 특정 팀을 조회하고 해당 팀 소속의 사용자 목록을 확인하는 코드를 작성해보자.
1
2
3
4
5
6
7
8
|
Team findTeam = entityManager.find(Team.class, team.getId());
List<Member> members = findTeam.getMembers();
for (Member member : members)
{
System.out.println(member.getName());
}
|
cs |
양방향 매핑에 있어 아래 내용을 반드시 숙지하고 주의하도록 하자.
- 객체의 양방향 관계는 사실 양방향 관계가 아니고, 서로 다른 단방향 관계 2개이다.
- 때문에, 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야한다.
- 두 객체 중 하나로 외래키를 관리해야하는데, 이 외래키를 관리하는 객체를 연관관계의 주인이라고 한다.
연관관계의 주인
- 연관관계의 주인만이 외래키를 관리한다.
- 주인이 아닌쪽은 읽기만 가능해야한다.
- 주인은 mappedBy 속성을 사용하지 않는다.
- 주인이 아니면 mappedBy 속성으로 주인을 지정해야한다.
그렇다면 위 시나리오에서는 누구를 주인으로 삼아야할까? 바로 Member이다. Member-Team 간의 조인을 하는 외래키 TEAM_ID가 MEMBER 테이블에 있어 외래키가 MEMBER 테이블에서 관리되기 때문이다.
여기서 문제, 아래 코드 중 사용자에게 소속 팀을 지정하는 올바른 방법은?
[1번]
1
2
3
4
5
6
7
8
9
10
11
|
Team team = new Team();
team.setName("팀A");
entityManager.persist(team);
Member member = new Member();
member.setName("사용자");
member.setTeam(team);
entityManager.persist(member);
|
cs |
[2번]
1
2
3
4
5
6
7
8
9
10
11
|
Team team = new Team();
team.setName("팀A");
entityManager.persist(team);
Member member = new Member();
member.setName("사용자");
team.getMembers().add(member);
entityManager.persist(member);
|
cs |
정답은 1번이다. 2번 코드에서는 연관관계의 주인인 member에 team이 입력되지 않아 실제 코드 수행 시 TEAM_ID 컬럼이 null이 입력된다.
그렇다면, Team 객체를 통해서 사용자를 소속팀에 추가할 수는 없는 것일까?
그렇지 않다. 연관관계 편의 메서드를 생성해 처리가 가능하다. 연관관계 편의 메서드는 이후 포스팅에서 다루도록 하자.
출처 :: 인프런 강의(자바 ORM 표준 JPA 프로그래밍 - 기본편)
'🌱 SPRING > JPA' 카테고리의 다른 글
[JPA] 다양한 연관관계 매핑 (0) | 2020.07.19 |
---|---|
[JPA] 양방향 매핑시 주의점과 연관관계 편의 메서드 (0) | 2020.07.18 |
[JPA] 기본키 매핑 어노테이션 (0) | 2020.07.04 |
[JPA] 매핑 어노테이션 (0) | 2020.06.13 |
[JPA] 스키마 자동 생성 (0) | 2020.06.13 |