JPA 연관 관계 - 양방향 연관관계와 연관과계의 주인

목차

양방향 연관관계의 문제점

객체에서는 각기 다른 객체에서 참조하는 객체를 업데이트 하는 방법이 두 가지가 발생한다.

양방향 연관관계의 해결 방법

객체의 두 관계중 하나를 연관관계의 주인 으로 지정해 주인만이 외래 키를 관리하게 한다.(등록, 수정)
주인이 아닌 쪽은 읽기만 가능하다

양방향 연관관계 주인 정하기

Foreign Key (외래키) 가 있는 곳을 주인으로 정해라

  • 연관관계의 주인은 mappedBy 속성을 사용할 수 없다.
  • 연관관계 주인이 아니면 mappedBy 속성을 사용할 수 있다.

연관관계 주인이 아닌 객체에 데이터 업데이트

Member member = new Member();
member.setUsername("member1");
entityManager.persist(member);

Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member);
entityManager.persist(team);

MEMBER 테이블 내 TEAM_ID 값이 들어가지 않은 것을 확인할 수 있다.

연관관계 주인인 객체에 데이터 업데이트

Team team = new Team();
team.setName("TeamA");
// team.getMembers().add(member);
entityManager.persist(team);

Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
entityManager.persist(member);

MEMBER 테이블 내 TEAM_ID 값이 정상적으로 들어간 것을 확인할 수 있다.

양방향 연관 관계 주의

양쪽에 데이터를 Setting 해 준다.

데이터가 영속성 Context 내에만 존재할 경우 연관된 데이터 조회시 정상적으로 조회하지 못하는 문제가 발생한다.
순수 객체 상태 를 고려해 항상 양쪽에 값을 설정한다.

  • Team과 Member는 영속성 Context에만 데이터가 있는 상태
  • Team 내 Member List에는 현재 아무 값이 없는 상태
  • 결과적으로 데이터를 못가져오게 된다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
em.persist(member);

Team findTeam = em.find(Team.class, team.getId()); // 1차 캐시
List<Member> members = findTeam.getMembers();
System.out.println("==========================");
for (Member m : members) {
System.out.println("m = " + m.getUsername());
}
System.out.println("==========================");

Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
em.persist(member);

team.getMembers().add(member);

Team findTeam = em.find(Team.class, team.getId()); // 1차 캐시
List<Member> members = findTeam.getMembers();
System.out.println("=====================================================================");
for (Member m : members) {
System.out.println("m = " + m.getUsername());
}
System.out.println("=====================================================================");

Flush와 Clear를

  • team.getMembers().add(member); 값을 넣지 않아도 데이터가 정상적으로 출력 된다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setUsername("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());
}

연관관계 편의 메서드 생성

Member 에 생성

public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
  • @JoinColumn 는 name 속성에 Mapping 할 외래 키를 지정한다.
@Entity
public class Member {

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "MEMBER_ID")
private long id;

@Column(name = "USERNAME")
private String username;

@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;

public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
}

Team 에 생성

public void addMember(Member member){
member.setTeam(this);
members.add(member);
}
  • mappedBy 는 양뱡향 연관관계일때 반대쪽에 매핑되는 Entity 필드 값을 준다.
@Entity
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "TEAM_ID")
private Long id;
private String name;

@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();

public void addMember(Member member){
member.setTeam(this);
members.add(member);
}
}

양방향 매핑시 무한 루프를 조심하자

Member에서 toString 생성시 Team 객체를 만날 경우 Team 객체의 toString을 불러온다. Team 객체내 toString은 Member 객체의 toString을 불러오면서 무한 루프가 생기게된다.

Member.java

@Override
public String toString() {
return "Member{" +
"id=" + id +
", username='" + username + '\'' +
", team=" + team + // 문제 발생
'}';
}

Team.java

@Override
public String toString() {
return "Team{" +
"id=" + id +
", name='" + name + '\'' +
", members=" + members + // 문제 발생
'}';
}
Share