(JPA)연관관계 매핑
JPA 연관관계 매핑
객체
멤버(N)
팀(1)
연관관계 매핑 미설정
모델링
객체
를테이블
에 맞추어모델링
참조 대신에 외래 키를 그대로 사용
객체
1
2
3
4
5
6
7
8
9
10
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String username;
@Column(name = "TEAM_ID")
private Long teamId;
}
- 멤버 객체
1
2
3
4
5
6
7
8
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
}
- 팀 객체
객체 저장
1
2
3
4
5
6
7
8
9
10
// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
// 멤버 저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);
Member Table
MEMBER_ID | TEAM_ID | USERNAME |
---|---|---|
1 | 1 | member1 |
Team Table
TEAM_ID | NAME |
---|---|
1 | TeamA |
객체 조회
1
2
3
4
5
6
7
8
// 멤버 조회
Member findMember = em.find(Member.class, member.getId());
// 멤버의 팀 아이디 가져오기
Long findTeamId = findMember.getTeamId();
// 멤버가 해당하는 팀 조회
Team findTeam = em.find(Team.class, findTeamId);
- 연관관계가 존재하지 않아, 필요할때 마다 JPA를 통해 계속 조회가 이뤄진다.
- 객체지향스럽지 않다.
- 테이블은 외래키로 조인을 사용해서 연관된 테이블을 찾고, 객체는 참조를 사용해서 연관된 객체를 찾는다.
- 테이블과 객체간에는 이런 큰 차이점이 있다.
연관관계 매핑 설정(단방향)
모델링
- 객체 지향 모델링
객체
1
2
3
4
5
6
7
8
9
10
11
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
- Member
N
이고, Team이1
이기 때문에ManyToOne
연관관계를 설정해준다. Team_ID(FK)
를 매핑해줘야 하기 때문에JoinColumn
을 사용해서 매핑할 컬럼을 지정해준다.- 위와 같이 설정해 주면 매핑이 끝나게 된다.
- Team 객체 같은 경우는 변경 사항이 없다.
객체 저장
1
2
3
4
5
6
7
8
9
10
// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
// 멤버 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);
- team의 id 값을 저장하는 것이 아니라 team 객체 자체를 저장
연관관계 매핑 설정(양방향)
- 양방향으로 객체 탐색이 가능
- 매핑은 단방향이 좋다.
DB 테이블
- 방향이 따로 존재 하지 않는다, FK, PK의 조인을 통해 양쪽으로 조회가 가능
- 테이블 연관관계
- 회원 <-> 팀의 연관관계 1개(양방향)
객체
객체 연관관계
- 객체의 양방향 관계는 사실 양방향 관계가 아니라
서로 다른 단방향
관계 2개다. 회원 -> 팀 연관관계 1개(단방향)
- 팀 -> 회원 연관관계 1개(단방향)
- 객체의 양방향 관계는 사실 양방향 관계가 아니라
위에 단방향 매핑으로는 member에서는 team을 조회 할 수 있으나, team에서는 member를 조회 할 수 없다.
- 객체와 DB 테이블의 가장 큰 차이점
1
2
3
4
5
6
7
8
9
10
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
- Team 객체에 member를 저장 할 수 있도록 List 생성
Member -> Team은 다대일, Team -> Member
일대다 이기 때문에@OneToMany
매핑 설정을 해준다.- 일대다 매핑에서 어떤 객체랑 연결되어 있는지 나타내주기 위해
mappedBy = "team"
을 설정Member
객체에 존재하는Team
의FK
가 저장되는 필드 명을mappedBy
로 지정해주면 된다.
- Member 객체는 변경 사항이 없다.
객체 조회
1
2
3
4
5
6
...
Member findMember = em.find(Member.class, member.getId());
List<Member> members = findMember.getTeam().getMembers();
for (Member m : members) {
System.out.println("m = " + m.getUsername());
}
- 멤버를 조회하고, 멤버를 통해 다시 팀을 조회 한 후 해당 팀에 속해 있는 멤버를 조회 할 수 있게되었다.
- 한쪽 방향에서만 조회가 되는것이 아니라 양방향으로 조회가 가능
연관관계 주인
Member 클래스 혹은 Team 클래스에서 서로 참조 하고 있는 두개의 필드 값중
하나로 외래 키를 관리
해야 한다.- 객체의 두 관계중
하나를
연관관계의 주인
으로 지정 연관관계의
주인만이
외래 키를 관리(등록, 수정)- 주인이 아닌쪽은
readOnly
주인
은 mappedBy 속성을사용하지 않는다.
주인이 아니면
mappedBy 속성으로주인 지정
- 누구를 주인으로?
- 외래키가 있는 객체를 주인으로 설정(영한님 가이드)
- 위의 예제에서는 Member를 주인으로 설정
양방향 매핑에서 가장 많이 하는 실수
1
2
3
4
5
6
7
8
9
10
11
Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member); // 문제를 일으키는 코드
em.persist(team);
Member member = new Member();
member.setName("member1");
/**/
em.persist(member);
- 연관관계 주인에 값을 입력하지 않았다.
3번째 라인에 작성되어 있는 코드는 연관관계 주인이 아닌곳에 값을 넣어 주기 때문에 DB에서 변경이 일어나지 않는다.
- 9번째 라인에
member.setTeam(team);
코드를 추가해서 연관관계 주인에 값을 넣어 줘야 DB에 Insert 된다. - 둘다 넣어도 상관 없지만, 주인의 값을 넣어주는 코드는 절때 빠지면 안된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
Team findTeam = em.find(Team.class, team.getId());
List<Member> members = findTeam.getMembers();
for (Member m : members) {
System.out.println("m = " + m.getUsername());
}
JPA 입장에서는 주인에만 값을 넣어줘도 상관 없지만(flush, clear가 이뤄진 경우), 객체 관점에서는 둘다 값을 넣어주는게 좋다.
flush와 clear가 제대로 이뤄지지 않으면, 영속성 컨텍스트에서 1차 캐쉬의 값을 조회해 오기 때문에 값이 없다.
분명 Team객체에 존재하는 members에 따로 member를 넣어 주지 않았지만, 조회를 하면 조회가 잘 된다.
- members의 데이터를 실제로 사용하는 시점에 query를 하나 사용해서 연관되어 있는 member 데이터를 전부 가져옴
- 늘
양방향 편의 메서드가 없어도 데이터가 잘 나오는데 왜 사용하는 거지?
라는 의문점이 있었는데 한방에 해결되었다.
REFERENCE
This post is licensed under CC BY 4.0 by the author.