티스토리 뷰
소스 코드는 여기 있습니다. (commit hash: e336ad7)
> git clone https://github.com/lcalmsky/jpa > git checkout e336ad7
Overview
양방향 연관관계를 설명합니다.
객체 지향 모델링
양방향 연관관계는 객체가 서로 참조할 수 있는 것을 의미합니다.
따라서 객체간의 관계는 아래 처럼 나타낼 수 있습니다.
테이블 연관관계는 이전과 동일합니다. DB에서는 team_id 만으로도 Member를 찾을 수 있기 때문입니다.
테이의 연관관계에는 방향이라는 개념이 없습니다. FK 하나로 충분히 연관관계를 파악할 수 있기 떄문입니다.
양방향 연관관계 구현
이전 포스팅에서 이미 단방향 연관관계를 구현해놓았기 때문에 양방향 관계를 맺기 위해선 Team
쪽에만 관계를 추가해주면 됩니다.
Member
에서 Team
을 @ManyToOne
으로 지정했으므로 Team
에서 Member
의 관계를 지정할 땐 그 반대로 해주시면 됩니다.
/src/main/java/com/tistory/jaimenote/jpa/domain/entity/Team.java`
package com.tistory.jaimenote.jpa.domain.entity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.ToString.Exclude;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString // (2)
public class Team {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team") // (1)
@Exclude // (2)
private List<Member> members = new ArrayList<>(); // (3)
private Team(String name) {
this.name = name;
}
public static Team withName(String name) {
return new Team(name);
}
}
(1) @OneToMany
애너테이션을 사용하고 mappedBy
속성을 team
으로 지정합니다. 이는 선언된 members
가 team
에 의해 매핑된다는 뜻입니다.
(2) @ToString
을 사용할 때 서로 순환참조 되어 무한루프에 빠질 수 있는 항목에 @Exclude
를 추가해줍니다.
(3) JPA
에서 Collection
을 사용할 때는 선언과 동시에 초기화해주는 것이 NullPointerException
을 방지하는데 도움이 됩니다.
Member.java 보기
package com.tistory.jaimenote.jpa.domain.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
private Member(String name) {
this.name = name;
}
public static Member withName(String name) {
return new Member(name);
}
public void join(Team team) {
this.team = team;
}
}
테스트
Team
을 조회해 Member
를 찾을 수 있는지 확인해보겠습니다.
/src/test/java/com/tistory/jaimenote/jpa/infra/repository/MemberRepositoryTest.java`
// 생략
@DataJpaTest
class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository;
@Autowired
TeamRepository teamRepository;
@PersistenceContext
EntityManager entityManager;
@BeforeEach
void setup() {
Team team = Team.withName("토트넘");
teamRepository.save(team);
Member member = Member.withName("손흥민");
member.join(team);
memberRepository.save(member);
entityManager.flush();
entityManager.clear();
}
// 생략
@Test
void findMemberByTeam() {
List<Team> teams = teamRepository.findAll();
for (Team team : teams) {
System.out.println(team);
System.out.println(team.getMembers());
}
}
}
데이터를 setup하는 부분을 제외하고 team과 member를 조회한 부분을 확인해보면,
Hibernate:
select
team0_.id as id1_1_,
team0_.name as name2_1_
from
team team0_
Team(id=1, name=토트넘)
Hibernate:
select
members0_.team_id as team_id3_0_0_,
members0_.id as id1_0_0_,
members0_.id as id1_0_1_,
members0_.name as name2_0_1_,
members0_.team_id as team_id3_0_1_
from
member members0_
where
members0_.team_id=?
2022-06-26 22:18:03.440 TRACE 31087 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
[Member(id=2, name=손흥민, team=Team(id=1, name=토트넘))]
이렇게 team_id
로 다시 member
를 찾아 출력한 것을 확인할 수 있습니다.
객체와 테이블의 관계 맺는 방식 차이
위에서 확인했듯이 양방향 연관관계의 경우 객체간 연관관계는 2개, 테이블간 연관관계는 1개가 됩니다.
객체입장에서는 양방향 관계는 곧 서로를 향한 단방향 연관관계를 의미합니다.
- Member -> Team
- Team -> Member
테이블 입장에서는 FK 하나로 두 테이블의 연관관계를 관리할 수 있습니다.
- member <-> team
따라서 테이블의 FK
와 같은 역할을 할 수 있는 값을 Member
또는 Team
객체에서 관리해주어야 합니다.
이 때 관리의 주체를 연관관계의 주인
이라고 부릅니다.
다음 포스팅에서 확인해보겠습니다.
'JPA' 카테고리의 다른 글
[JPA] 연관관계 매핑 예제 (0) | 2022.07.03 |
---|---|
[JPA] 연관관계의 주인 (0) | 2022.06.30 |
[JPA] 단방향 연관관계 (0) | 2022.06.28 |
스프링 부트 JPA 예약어 컬럼명 사용하기 (0) | 2022.06.27 |
[JPA] 사용시 연관관계가 필요한 이유 (0) | 2022.06.25 |
- Total
- Today
- Yesterday
- intellij
- Spring Data JPA
- 헥사고날 아키텍처
- 스프링 부트
- 함께 자라기 후기
- 알고리즘
- 스프링 부트 회원 가입
- r
- proto3
- JSON
- spring boot jwt
- 클린 아키텍처
- Spring Boot JPA
- 함께 자라기
- leetcode
- spring boot application
- Spring Boot
- Linux
- spring boot app
- Jackson
- JPA
- gRPC
- 스프링 부트 튜토리얼
- 스프링 부트 애플리케이션
- Java
- QueryDSL
- 스프링부트
- Spring Boot Tutorial
- 스프링 데이터 jpa
- @ManyToOne
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |