Reference. 자바 ORM 표준 JPA 프로그래밍
책 목차 및 이전 글
더보기
들어가기 전 JPA 특징, Q&A
1. JPA 소개
3. 영속성 관리
4. 엔티티 매핑
4.1 - 4.3 @Entity, @Table, 다양한 매핑
4.4 - 4.5 데이터베이스 스키마 자동 생성, DDL 생성 기능
5. 연관관계 매핑 기초
5.3 양방향 연관관계
JPA 양방향 관계
- 회원 → 팀 (Member.team) [다대일]
- 팀 → 회원 (Team.members) [일대다]
<참고>
- JPA는 List를 포함해서 Collection, Set, Map 같은 다양한 컬렉션을 지원(14.1절 참고)
데이터베이스 양방향 관계
- 데이터베이스 테이블은 외래 키 하나로 양방향 조회 가능
MEMBER JOIN TEAM
,TEAM JOIN MEMBER
가능
5.3.1 양방향 연관관계 매핑
Member.java
@Entity
public class Member {
...
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
...
}
Team.java
@Entity
pubilc class Team {
...
@OneTomany (mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
//Getter, Setter ...
}
- 팀과 회원은 일대다 관계, 팀 엔티티에 컬렉션인 List<Member> member를 추가
- 일대다 관계를 매핑하기 위해
@OneToMany
매핑 정보를 사용
mappedBy
속성은 양방향 매핑일 때 사용 (반대쪽 매핑의 필드를 할당)
5.3.2 일대다 컬렉션 조회
Team team = em.find(Team.class, "team1");
List<Member> members = team.getMembers(); //(팀 -> 회원)
for (Member member : members) {
System.out.println("member.username = " + member.getUsername());
}
//결과
//member.username = 회원1
//member.username = 회원2
5.4 연관관계의 주인
mappedBy가 필요한 이유
- 객체에 양방향 관계는 존재하지 않고 연관관계 2개를 잘 묶어서 표현
- 객체 연관관계 표현
- 회원 → 팀 연관관계 1개 (단방향)
- 팀 → 회원 연관관계 1개 (단방향)
- 테이블 연관관계 표현
- 회원 <-> 팀의 연관관계 1개(양방향)
- 엔티티를 양방향 연관관계로 설정하면 객체 참조는 둘인데 외래키는 하나인 문제점 발생
- JPA는 두 객체 연관관계 중 하나를 정해서 테이블 외래키를 관리, 이것이 연관관계 주인
5.4.1 양방향 매핑의 규칙: 연관관계의 주인
- 양방향 연관관계는 두 연관관계 중 하나를 주인으로써 지정해야 함
- 연관관계의 주인만이 데이터베이스 연관관계와 매핑하고 외래 키(등록, 수정 삭제)를 관리
- 주인이 아닌 쪽은 읽기만 가능
mappedBy
: 연관관계의 주인으로 지정하는 속성으로 사용- 주인은
mappedBy
속성을 사용하지 않음
- 주인이 아니면
mappedBy
속성을 사용해 속성의 값으로 연관관계 주인을 지정
- 주인은
- 연관관계의 주인을 정한다는 것은 외래 키 관리자를 선택하는 것
- 회원 엔티티에 있는
Member.team
은 자기 테이블에 외래 키를 관리 (MEMBER.TEAM_ID
)
- 팀 엔티티에 있는
Team.members
를 선택하면 물리적으로 다른 테이블의 외래키를 관리
5.4.2 연관관계의 주인은 외래 키가 있는 곳
- 회원 테이블이 외래 키를 가지고 있으므로
Member.team
이 주인
- 주인이 아닌
Team.members
에는mappedBy="team"
속성을 통해 주인이 아님을 설정
- 연관관계의 주인
mappedBy
속성 사용X
- 데이터베이스 연관관계와 매핑
- 외래 키를 관리
- 주인이 아닌 반대편
mappedBy
속성 사용
- 읽기만 가능
- 외래 키를 변경하지 못함
<참고>
- 데이터베이스 테이블의 다대일, 일대다 관계에서는 항상 다 쪽이 외래키
- @ManyToOne은 항상 연관관계의 주인, mappedBy를 설정할 수 없으므로 속성도 없음
5.5 양방향 연관관계 저장
JPA를 통한 저장
public void testSave() {
//팀1 저장
Team team1 = new Team("team1", "팀1");
em.persist(team1);
//회원1 저장
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); //연관관계 설정 member1 -> team1
em.persist(member1);
//회원2 저장
Member member2 = new Member("member2", "회원2");
member2.setTeam(team1); //연관관계 설정 member2 -> team1
em.persist(member2);
}
- 양방향 연관관계는 연관관계의 주인이 외래 키를 관리
- 주인이 아닌 방향은 설정하지 않아도 데이터베이스 외래 키 값이 정상 입력
//이런 코드가 필요해 보이지만 주인이 아니므로 영향X team1.getMembers().add(member1); team1.getMembers().add(member2);
5.6 양방향 연관관계의 주의점
- 양방향 연관관계의 흔한 실수는 주인에 값을 입력하지 않고 주인이 아닌 곳에 입력하는 실수
- 데이터베이스에 외래키 값이 정상적으로 저장되지 않았다면 이것부터 의심
team1.getMembers().add(member1);
team1.getMembers().add(member2);
5.6.1 순수한 객체까지 고려한 양방향 연관관계
- 객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전
- 모두 입력하지 않은 경우 JPA를 사용하지 않는 순수한 객체에선 심각한 문제 발생
- ORM은 객체와 관계형 데이터베이스 둘 다 중요
순수한 객체를 통한 테스트 코드
Team team1 = new Team("team1", "팀1");
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");
member1.setTeam(team1); //연관관계 설정 member1 -> team1
member2.setTeam(team1); //연관관계 설정 member2 -> team1
List<Member> members = team1.getMembers();
System.out.println("member.size = " + members.size());
//결과: members.size = 0
양방향 모두 설정한 테스트 코드
Team team1 = new Team("team1", "팀1");
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");
member1.setTeam(team1); //연관관계 설정 member1 -> team1
team1.getMembers().add(member1) //연관관계 설정 team1 -> member1
member2.setTeam(team1); //연관관계 설정 member2 -> team1
team1.getMembers().add(member2) //연관관계 설정 team1 -> member2
List<Member> members = team1.getMembers();
System.out.println("member.size = " + members.size());
//결과: members.size = 2
JPA를 추가한 테스트 코드
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
//양방향 연관관계 설정
member1.setTeam(team1); //연관관계 설정 member1 -> team1
team1.getMembers().add(member1) //연관관계 설정 team1 -> member1
em.persist(member1);
Member member2 = new Member("member2", "회원2");
//양방향 연관관계 설정
member2.setTeam(team1); //연관관계 설정 member2 -> team1
team1.getMembers().add(member2) //연관관계 설정 team1 -> member2
em.persist(member2);
- 위에 과정을 거쳐 연관관계를 설정하면 순수한 객체 상태 및 테이블 외래 키 정상입력도 가능
5.6.2 연관관계 편의 메서드
- 양방향 관계를 설정하다보면 실수로 둘 중 하나만 호출해서 양방향이 깨지기 쉬움
- 이런 실수를 줄이기 위해 한번에 설정하는 메소드를 연관관계 편의 메소드
연관관계 편의 메서드의 예시
//Member.java
public class Member {
private Team team;
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
...
}
---------------------------------------------------
//리팩토링 전체 코드
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); //양방향 설정
em.persist(member1);
Member member2 = new Member("member2", "회원2");
member2.setTeam(team1); //양방향 설정
em.persist(member2);
5.6.3 연관관계 편의 메소드 작성 시 주의사항
setTeam()
메소드에는 버그가 존재 (리팩토링 전에도 존재)
member1.setTeam(teamA); //1
member1.setTeam(teamB); //2
Member findMember = teamA.getMember(); //member1이 여전히 조회
teamB
로 변경할 때teamA
→member1
관계를 제거 X- 연관관계를 변경할 때는 기존 팀과 회원의 연관관계를 삭제 후 추가가 필요
//Member.java public void setTeam(Team team){ //기존 팀과 관계를 제거 if (this.team != null) { this.team.getMembers().remove(this); } this.team = team; team.getMembers().add(this); }
- 단방향 연관관계 2개를 양방향인 것처럼 보이게 하려면 많은 수고가 필요
- 반면 관계형 데이터베이스는 외래 키 하나로 문제를 단순하게 해결
- 객체에서 양방향 연관관계를 사용하려면 견고한 로직이 필요
<참고>
teamA
→member1
관계가 제거 되지 않아도 외래 키 변경에는 문제가 없음
- 연관관계의 주인인
Member.team
의 참조를teamB
로 변경했으므로 정상 반영
- 문제는 관계 변경 후 영속성 컨텍스트가 있는 경우
teamA
의getMembers()
은member1
을 반환- 이런 문제를 해결하기 위해 관계를 제거하는 것이 안전한 방법
정리
- 양방향의 장점은 반대방향으로 객체 그래프 탐색 기능이 추가된 것 뿐 (단방향 2개)
member.getTeam(); //회원 -> 팀 team.getMembers(); //팀 -> 회원 (양방향 매핑으로 추가된 기능)
- 주인의 반대편은 mappedBy로 주인을 지정
내용 정리
- 단방향 매핑만으로 테이블과 객체 연관관계 매핑은 이미 완료
- 단방향을 양방향으로 만들면 반대바향으로 객체 그래프 탐색 기능이 추가
- 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리
※연관관계의 주인을 정하는 기준
단방향
은 항상 외래 키가 있는 곳을 기준으로 매핑
양방향
은 비즈니스 로직상 더 중요하다고 주인으로 선택하면 안됌
양방향
은 비즈니스 중요도를 배제하고 단순히 외래 키 관리자 정도의 의미만 부여
- 연관관계의 주인은 외래 키의 위치와 관련해서 정해야지 비즈니스로 접근하면 안됌
<주의>
- 양방향 매핑시 무한루프에 빠지는 것에 주의
- 예)
Member.toString()
에서getTeam()
을 호출,Team.toString()
에서getMember()
호출시 무한 루프 발생
- 이 문제는 JSON 변환할때 자주 발생, 라이브러리들은 어노테이션 기능을 제공해서 해결
- 예)
<참고>
- 일대다를 연관관계의 주인으로 선택하는 것이 가능 (6.2.1절 참고)
Team.members
를 연관관계의 주인으로 선택이 가능
- 하지만 성능과 관리 측면에서 권장하지 않음
'개발서적 > 자바 ORM 표준 JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 6.2 일대다 (0) | 2021.08.18 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 6.1 다대일 (0) | 2021.08.18 |
[자바 ORM 표준 JPA 프로그래밍] 5.2 연관관계 사용 (0) | 2021.08.12 |
[자바 ORM 표준 JPA 프로그래밍] 5.1 단방향 연관관계 (0) | 2021.08.12 |
[자바 ORM 표준 JPA 프로그래밍] 4.7 필드와 컬럼 매핑: 레퍼런스 (0) | 2021.08.03 |