public class JpaMain {
public static void main(String[] args) {
...
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);
em.persist(member);
}
}
insert into Member(team_id, username, member_id) values (?, ?, ?)
insert into Team(name, team_id) values (?, ?)
TEAM_ID
가 저장되지 않았다.insert
, update
시 읽기 전용 필드를 고려하지 않는다.public class JpaMain {
public static void main(String[] args) {
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
// 연관 관계의 주인인 Member.team에 추가한다.
member.setTeam(team);
em.persist(member);
}
}
team.getMembers().add(member);
가 아닌 member.setTeam(team)'
코드를 작성해야한다.
연관관계의 주인에 값을 꼭 입력하자
public class JpaMain {
public static void main(String[] args) {
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());
}
}
}
위 코드를 자세히 살펴본다.
team
, member
를 em.persist()
를 통해 영속 상태로 만들어 주었고,
member.setTeam(team)
코드를 통해 연관관계의 주인데 값을 넣어주었다.
이후em.flush()
플러시를 통해 DB에 직접 insert Query
를 넣어주었다.
여기서 em.find(Team.class, team.getId())
코드가 실행되면 영속성 컨텍스트에 존재하는 team의 Id를 조회해서, findTeam
객체를 얻게 된다.
List<Member> members = findTeam.getMembers();
코드를 살펴보자
우리는 위 코드에서 Team
엔티티의 members
필드에 값을 추가해주지 않았다.
하지만 코드 실행 시 select Query
가 나가면서 원하는 값 m = member1
이 출력된다.
=> 양쪽 모두에 값을 설정하지 않았지만, 직접 em.flush()
코드를 통해 DB에 데이터를 추가하였기 때문에 select Query
를 통해 값을 얻을 수 있다.
만약 위 코드에서 em.flush()
코드를 실행하지 않으면 어떤 결과가 나올까?
team
은 em.persist(team)
코드를 통해 영속상태로 전환되고 1차 캐시에 들어간다.
우리는 team
의 members
필드에 어떤 값도 추가하지 않았다.
Team findTeam = em.find(Team.class, team.getId());
코드가 실행되면 영속성 컨텍스트에 존재하는 team의 Id를 조회해서, findTeam
객체를 얻게 된다.
이후 List<Member> members = findTeam.getMembers();
코드가 실행될 때
1차캐시에도 DB에도 findTeam
에 어떤 member가 존재하는지에 대한 정보가 없으므로 어떠한 결과도 출력되지 않는다.
commit()
시점에 insert Query
만 나갈 뿐이다.
∴ 따라서 양쪽 모두 값을 설정하도록 주의한다.
public class Member {
...
// 연관 관계 메서드
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
Member
엔티티에서 연관 관계 메서드를 만든다.
this.team = team
코드를 통해 연관관계의 주인에 값을 넣을 수 있고
team.getMembers().add(this)
코드를 통해 반대 쪽에도 값을 넣을 수 있다.
즉, 연관관계 편의 메서드를 통해 양쪽에 값을 설정하는 것을 원자적으로 실행할 수 있는 것이다.
public class Member {
@Override
public String toString() {
return "Member{" +
"id=" + id +
", name=" + name + '\'' +
// 무한 루프
", team=" + team + '\'' +
'}';
}
}
public class Team {
@Override
public String toString() {
return "Member{" +
"id=" + id +
", name=" + name + '\'' +
// 무한 루프
", members=" + members + '\'' +
'}';
}
}
Member
, Team
엔티티에 모두 toString()
을 오버라이딩 했다.
만약, 어떠한 Team엔티티를 출력한다고 가정해보자.
Team
의 toString()
메서드에 members
즉, member
엔티티의 배열이 존재한다.
Member
엔티티의 toString()
메서드에는 team
이 존재한다.
toString()
메소드 사이에서 무한루프가 발생하게 된다.
양방향 매핑
가능하면 단방향 매핑으로 끝내도록 한다.
(단방향 매핑만으로도 이미 연관 관계 매핑은 완료된다.)
양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색)하는 기능을 추가한 것 뿐이다.
-> 양방향을 매핑을 하면 고려할 것들만 많아진다.
실무에서 JPQL로 역방향 참조가 필요한 경우 그때 추가하면 된다.
-> 단방향 매핑을 잘 해놓은 다음, 양방향은 필요할 때 사용
연관관계의 주인을 정하는 기준
비즈니스 로직 기준 X, 외래 키의 위치 기준 O
-> N(다)에 해당하는 엔티티를 주인으로 정한다.
참고 :
김영한. 『자바 ORM 표준 JPA 프로그래밍』. 에이콘, 2015.