궁금한 상황이 생겼다.
JPA 양방향 연관 관계 시, 연관 관계의 주인에서만 연관 관계 설정이 가능하다고 한다.
연관 관계 주인 엔티티(Member)에서 setter()로 관계 엔티티(Team)를 설정해주면
데이터베이스 테이블에선 외래키(TEAM_ID)가 입력되어, 그 외래키로 JOIN 하여 연관 관계를 매핑할 수 있다.
-> "연관 관계 주인만이 DB 연관 관계와 매핑됨"
@Entity
@Getter
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne
private Team team;
//연관관계설정
public void setTeam(Team team){
this.team = team;
}
public Member() {}
public Member(String name) {
this.name = name;
}
}
이렇게 연관 관계의 주인(다대일 에서 "다")에서만 setter 메소드를 설정했을 때,
관계 엔티티에서는 당연하게 객체 그래프 탐색이 불가능하다.
@Test
@Transactional
@Rollback()
void test2(){
Team team1 = new Team("팀1");
em.persist(team1); //영속성 컨텍스트에 저장
Member member1 = new Member("회원1");
member1.setTeam(team1);
em.persist(member1); //영속성 컨텍스트에 저장
Member member2 = new Member("회원2");
member2.setTeam(team1);
em.persist(member2); //영속성 컨텍스트에 저장
assertThat(team1.getMembers().size()).isEqualTo(0) //true 객체 그래프 탐색 x...
}
음, 하지만 JPA에서
"연관관계 주인만이 DB에 연관관계와 매핑되고 외래키를 관리한다. '주인이 아닌 반대편은 읽기만 가능하다'" 라고 했으니
주인이 아닌 엔티티에서도 읽기는 가능해야한다.
그럼 영속성 컨텍스트의 flush가 이루어지고 나서는 Team 엔티티로 Member엔티티를 읽을 수 있는 것일까 ?
검증을 해보자...
@Test
@Transactional
@Rollback()
void test2(){
Team team1 = new Team("팀1");
em.persist(team1); //영속성 컨텍스트에 저장
Member member1 = new Member("회원1");
member1.setTeam(team1);
em.persist(member1); //영속성 컨텍스트에 저장
Member member2 = new Member("회원2");
member2.setTeam(team1);
em.persist(member2); //영속성 컨텍스트에 저장
assertThat(team1.getMembers().size()).isEqualTo(0); // 순수 객체에서는 연관관계 못찾음
em.flush(); //영속성 컨텍스트 플러시 후 비우기
em.clear();
//영속성 컨텍스트 플러시 일어난 후, 주인이 아닌 엔티티로도 연관관계 찾을 수 있을까?
assertThat(em.find(Team.class, team1.getId()).getMembers().size()).isEqualTo(0);
}
결과 :
Team 엔티티로 연관된 Member를 찾을 수 있었다.
여기서 하나 더 확인하기.
Team 엔티티로 Member를 찾는 코드를 작성하였으나,
사실 데이터베이스 상으로는 Member의 "외래키"인 TEAM_ID로 찾아야 하는 것이므로
연관 관계의 주인인 Member를 이용하여 Team을 찾는 쿼리가 자동 생성되었을 것이다.. 맞을까..?
Team findTeam = em.find(Team.class, team1.getId()); // Team 찾기
assertThat(findTeam.getMembers().size()).isEqualTo(2); // Team과 Member의 연관 관계 찾기
결과:
뚜둔~
Team엔티티를 꺼내어 오고, Team엔티티로 Member를 찾는 코드를 작성하였지만,
연관 관계의 주인은 Member 엔티티이므로
"select @@@ from Member where Member.team_id = @@"
라는 Member 엔티티로 Team 엔티티를 조회하는 쿼리가 자동 생성 되었다 ~~
* 실무에선 순수한 객체에서도 객체 그래프 접근이 되어야하므로 setter()설정을 양방향으로 해주자 !
- 하지만 각각의 엔티티에서 setter() 설정을 하는것보다, 연관관계 주인(외래키가 있는) 엔티티에서 하나의 메소드로 처리하는 것이 좋다 !
public class Member {
private Team team;
public void setTeam(Team team){
if(this.team != null){ //기존팀과의 관계를 제거
this.team.getMembers().remove(this);
}
this.team = team; //연관관계의 주인 -> 외래키를 관리 //양방향 setter() 설정
team.getMembers().add(this); //주인이 아니므로 DB저장시 사용되진 않음 //양방향 setter() 설정
}
}
public void test(){
Team team1 = new Team("team1", "팀1");
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");
member1.setTeam(team1);
member2.setTeam(team2);
List<Member> members = team1.getMembers().size(); // 2
}
결론 :
1.
영속성 컨텍스트의 flush가 이루어지지 않은 상태에서
"순수한 객체"상태에서 연관관계 주인이 아닌 엔티티로 연관관계 찾는 것은 불가능 !
2.
영속성 컨텍스트가 flush되고나서 find()로 꺼내왔을 경우,
더이상 "순수한 객체"가 아니게 되고,
따라서 연관관계 주인이 아닌 엔티티로 연관관계 찾는 것은 가능 !
3.
하지만!
연관관계 주인이 아닌 엔티티로 JPA 코드를 작성하였다 하더라도
실제 생성되는 데이터베이스 쿼리는
"연관관계 주인 테이블의 '외래키'"를 통하여 찾는 것으로 자동 생성된다 ~~!!
4.
실무 적용 시 양방향에서 객체 그래프 탐색이 필요하므로
연관관계를 맺어줄 땐 주인 엔티티에서 내부적으로 양뱡향으로 set하는 메소드를 정의하여 사용하자 !!
-> (주인 엔티티에서 내부적으로 양뱡향으로 set하는 메소드)
public class Member {
private Team team;
public void setTeam(Team team){
if(this.team != null){ //기존팀과의 관계를 제거
this.team.getMembers().remove(this);
}
this.team = team; //연관관계의 주인 -> 외래키를 관리 //양방향 setter() 설정
team.getMembers().add(this); //주인이 아니므로 DB저장시 사용되진 않음 //양방향 setter() 설정
}
}
'백엔드 > JPA' 카테고리의 다른 글
JPA 영속성 전이 정리 - (영속성 컨텍스트 및 엔티티 생명주기, 영속성 전이 옵션과 insertable=false, updatable=false 설정) (3) | 2024.04.15 |
---|---|
복합키 JPA 사용 시 연관관계 및 1차 캐시 주의 사항 - 2 (0) | 2024.01.13 |
복합키 JPA 사용 시 연관관계 및 1차 캐시 주의 사항 (2) | 2024.01.06 |