JPA입장에서는, 연관관계의 주인에게만 값을 넣어주면 되지만,
사실 객체지향 입장에서는 둘 다 넣어줘야 한다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);
tx.commit();
여기서 보면,
DB상으로는 문제가 없다.
Team 생성한 다음에, persist 해서 id까지 완벽히 넣고 영속성 컨텍스트에 까지 넣고,
Member를 만들어 그 만든 team을 넣어주고, Member도 마찬가지로 영속성 컨텍스트에 넣어준다.
DB상으로는 제대로 반영이 되었다.
근데, 객체관점에서는
team의 members에 들어가 있지 않은 상태이다.
만약 여기서 em.find(Member.class,member.getId()).getTeam();
해서 가져온다고 생각해 보자.
이거는 한 트랜잭션이고, 우리가 따로 em.flush() + em.clrear()를 하지 않았기 때문에,
이미 영속성 컨텍스트에 1차 캐시로 가지고 있어서,
member에 넣은 team에 members를 넣어주지 않았기 때문에 그냥 그 빈 members를 가진 team을 가지고 온다.
그래서 문제다.
em.flush() + em.clrear() 하고 가져오면 영속성 컨텍스트에 없어서 select 해서 제대로 모두 조회해서 가지고 오겠지만.
여기서 재밌는 게 지연 뭐라고,
저렇게 연관관계 있을 경우
그 연관관계 있는 것을 호출하기 전 까지 DB에 값을 달라고 쿼리를 날리지 않는다.
member같은 경우는 team이 연관관계니까 team을 호출하기 전 까지 DB에 team을 가져오라고 쿼리를 날리지 않음.
왜냐하면, 복잡한 애플리케이션 경우 연관관계가 복잡할 수 있다.
근데 그 경우 연관관계를 다 가져오면 비효율적이다.
그래서, JPA는 호출을 해야지 비로소 그 때서야 select 해서 그 연관관계의 데이터들을 DB에서 가져온다.
호출을 해야만 가져온다.
여튼, 양방향 연관관계는 항상 둘 다 값을 set 해줘야 한다.
이럴 경우, 하면 좋을 게,
뭐 연관관계 편의 메소드 라고도 부르는데,
Member가 주인이니(더 명시적이기 때문에), Member에다가
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
이렇게 자기 자신을 추가해 주는 거다.
뭐 이것도 주인에다 할 지 하위에다가 할 지 상황마다 다르긴 하고, 자신이 선택하면 된다고 했다.
만약 team에 필요하다고 하면,
public void addMember(Member member) {
member.setTeam(this);
members.add(member);
}
기존의 get, set같은 경우는 JPA나 스프링 등에서 활용할 수도 있으니,
아예 새 메소드를 만들어 내가 Member에 team을 넣는 것은 저걸로 쓰는 것이다.
원래 저렇게 하면 좀 더 복잡하기는 하다.
근데 그렇게 깊이 있게 쓸 일은 없다고 한다.
실무에서도 여기까지만 대부분 해도 괜찮을 거라고 한다.
근데 여기서 주의할 점.
무한루프
예를 들어, 먼저 toString을 보겠다.
다음은, 인텔리제이의 기능으로 자동으로 만들어 주는 toString() 이다.
@Override
public String toString() {
return "Member{" +
"id=" + id +
", name='" + name + '\'' +
", team=" + team +
'}';
}
이 기능을 쓰면 이렇게 만들어 주는데,
team의 toString을 호출한다.
다음은, Team의 toString을 해 보겠다.
@Override
public String toString() {
return "Team{" +
"id=" + id +
", name='" + name + '\'' +
", members=" + members +
'}';
}
저것도, members의 toString을 호출한다.
저거는 member 하나하나 toString을 다 호출한다.
그럼 또 team을 toString 했다가..
또 members의 member들을 toString 했다가..
또 member안에 있는 team을 toString 했다가..
또 team안의 members의 member들을..
끝이 없다. 무한루프다.
이거 말고도 lombok, JSON 등.
JSON도 team을 JSON으로 한다고 치면
team의 id, name 하고,
members도 각각의 member를 json 형태로 만드는데 또 member안에 team이 있으므로 team을 json 형태로 만드는데 team 안에는 또 members가 있으므로...
조심해야 한다.
toString(), lombok, JSON 등
근데 보통 요새는 API로 많이 쓰는데, 그럼 컨트롤러에서 @Entity 반환하면 안되는 거냐?
네, 반환하지 마세요.
이유는, 우선 이렇게 무한루프에 빠질 수도 있고,
나중에 @Entity 바꿀 요지가 있다.
근데 @Entity 그대로 반환 시 약속해 뒀던 API 스펙이 바뀌게 된다.
그래서, Dto로 만들어 반환하는 게 좋다.
여튼, 근데
양방향 매핑은 필요할 때만 mapped by해서 넣는 걸 추천한다. 위와 같이 저거 말고도 여러가지 문제를 초래할 수 있다.
사실, 단방향 매핑만으로도 이미 연관관계의 매핑은 완료이다.
단방향
양방향
테이블만 보면 이미 테이블은 단방향 이기 때문에.
양방향 매핑은, 반대쪽에서 주인쪽으로 조회기능이 추가된 것 뿐이다.
근데 또 JPQL 하다보면 역방향으로 탐색해야 할 때도 많다.
그럴 때만 추가해 주면 된다. 필요할 때만.
위 보면, 양방향 매핑 추가가
Team에 List members 이거 하나 추가한 것이다.
@OneToMany(mapped by = "team")
private List<Member> members = new ArrayList<>();
그러니까, 단방향 매핑으로 일단 클래스 설계를 마친 다음에,
필요할 때만 양방향 추가. 위에 저거 넣는 것 처럼.
객체지향 관점에서 볼 때 양방향이 이득이 많이 없다고 함.
일대다 일 때 다 쪽에 연관관계 넣고 필요할 때만 양방향.
'JPA > JPA 기본' 카테고리의 다른 글
19. N : 1 다대일, 다양한 연관관계 (0) | 2023.10.24 |
---|---|
18. 실전2 객체구조를 외래키에서 참조로 (1) | 2023.10.24 |
16. 양방향 연관관계와 연관관계의 주인 1 (0) | 2023.10.22 |
15. 연관관계 매핑 (0) | 2023.10.22 |
14. 예제 (0) | 2023.10.22 |