이 글은 이전글과 이어지는 글입니다.
양방향 연관관계 매핑시 가장 많이 하는 실수 !!
(연관관계의 주인에 값을 입력하지 않음)
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member(); member.setName("member1");
//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);
em.persist(member);
위와 같이 연관관계의 주인은 member인데 team객체에서 멤버를 참조해서 member를 넣으면 결과가 어떻게 나올까요 ?
![[JPA] 양방향 연관관계와 연관관계의 주인2- 주의점,정리 - 양방향 연관관계 매핑시 가장 많이 하는 실수 !! [JPA] 양방향 연관관계와 연관관계의 주인2- 주의점,정리 - 양방향 연관관계 매핑시 가장 많이 하는 실수 !!](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
결과는 member 테이블 에는 teamID가 null 이 들어갑니다.
그러면 이것을 해결하기위해 어떻게 해야되나?
양방향 매핑시 연관관계의 주인에 값을 입력해야 한다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
team.getMembers().add(member);
//연관관계의 주인에 값 설정
member.setTeam(team); //**
em.persist(member);
(순수한 객체 관계를 고려하면 항상 양쪽다 값을 입력해야 한다)
![[JPA] 양방향 연관관계와 연관관계의 주인2- 주의점,정리 - 양방향 연관관계 매핑시 가장 많이 하는 실수 !! [JPA] 양방향 연관관계와 연관관계의 주인2- 주의점,정리 - 양방향 연관관계 매핑시 가장 많이 하는 실수 !!](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
위의 사진과 같이 TEAM_ID가 잘 들어가 있다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUser("member1");
member.setTeam(team);
em.persist(member);
team.getMembers().add(member);
em.flush();
em.clear();
Team findTeam = em.find(Team.class, team.getId());//1차 캐시
List<Member> members = findTeam.getMembers();
for(Member m : members) {
System.out.println("m = " +m.getUsername());
}
tx.commit();
근데 위와 같은 코드에서
team.getMembers().add(member);을 주석처리하고 실행시켜도 for문의 m.getUsername은 잘 출력이된다.
왜냐하면 jpa에서는 이거를 즉시로딩으로 Team객체의 members의 데이터를 사용하는 시점에 필요한 값들을 쿼리문을 다 날려서 가지고 오게됩니다. 그래서 굳이 team.getMembers().add(member); 이것을 안 넣어줘도 값을 가져옵니다.
이러면 객체지향스럽지 않다는 생각을 하게 됩니다.
그리고 team.getMembers().add(member)를 주석처리한 상태에서 em.flush를 해주면 괜찮은데 만약에 flush를 안해준 상태(em.flush() 와 em.clear()를 주석처리한 상태) 에서 em.find로 team을 가져와 멤버를 찾으면
m.getUsername()은 출력되지 않습니다.
이유는 영속성컨텍스트에 즉, 1차캐시에 team객체는 em.persist(team)을 했을때 들어가게 됩니다. 이때는
team객체에 member가 없어서 아무것도없는상태로 team객체 그대로 영속성컨텍스트에 들어가게 됩니다. 그래서 member를 출력하면 아무것도 없는 상태이므로 안나오게 되고 flush를 해서 insert문을 날리고 clear로 1차캐시를 비워준 후에 다시 team객체를 찾아오는데 어? member가 필요하네 하면서 즉시로딩으로 다 가져오게 되므로 team객체에는 member가 들어가게 됩니다.
그래서 양방향 연관관계를 맺을때에는 객체 지향적으로 생각해도 위와같이 team객체에 member를 add 해주는게 맞다. member에도 team을 넣어주고, team에도 member를 넣어주어야 한다.
member.setTeam(team);
team.getMembers().add(member);
결국엔 위와같이 값을 넣어주어야 하는데 사람이라면 실수를 하거나 깜빡할수있는 경우가 생긴다 ㅎㅎ..
그러면 어떻게 하면 될까요 ? 이럴때 권장하는 방법이 있습니다. 바로 연관관계 편의 메소드 입니다.
연관관계 편의 메소드
Member 엔티티에 setTea을 할때 team에 member을 넣어줍니다.
@Entitiy
public class Member{
...
@ManyToOne
@JoinColum(name = "TEAM_ID")
private Team team;
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
...
}
그리고 team.getMembers().add(member) 이 코드를 그냥 지워 버리면 됩니다.
또 하나, 연관관계편의 메소드 같은 경우는 setTeam을 해주는게 아니라 changetTeam과 같은 메소드 이름을 바꿔 줍니다. 그러면 getter, setter에 의한 관례로 그냥 해주는게 아닌 '되게 중요하게 뭔가를 하는거구나' 하고 알수가 있어 나중에 유지보수나 코드를 남이 볼때 좋을 수 있어 권장하는 방법입니다.
또 다른 하나는 Team객체에사용하는 방법인데요.
@Entity
public class Team{
...
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
public void addMember(Member member) {
member.setTeam(this);
}
...
}
위와 같이 Team객체에 addMember라는 연관관계편의 메소드를 만들어 넣어주는 방법도 있습니다.
그리고 연관관계편의 메소드는 두 객체에게 값을 넣어주고 있기때문에 둘중에 하나만 본인이 만드는 애플리케이션 상황에서 맞는것을 선택하여 하나만 사용하시면 됩니다!
양방향 매핑시에 무한루프를 조심
- toString()
- lombok
- JSON 생성 라이브러리
위와같은 경우에서 문제가 되는 경우가 많습니다.
Member객체에서 toString을 생성하면
@Overrid
public String toString(){
return "Member{" +
"id="+ id +
", username='" + username + '\'' +
", team=" + team +
'}';
}
위와같은 코드에서 잘보시면 id,username,team을해서 team으로 가면
team에서도 또 toString()이 호출 됩니다.
@Overrid
public String toString(){
return "Team{" +
"id="+ id +
", name='" + name + '\'' +
", members=" + members +
'}';
}
그러면 또 members에서 또 List안에 있는 member들의 toString()을 호출하게되고 다시 또 member로 가면 team을 호출하고...무한 루프가 돌게 됩니다.toString()을 출력하면 StackOverflowError가 뜹니다.그러면서 서버가 뻗어 버립니다.
JSON 생성 라이브러리는 controller의 respon에서 Entity를 직접 반환할때 Entity를 JSON으로 바꿀때 멤버에 팀이 있네?
팀을 또 JSON으로 바꾸는데 members의 member가 있네? 하면서 또 무한루프가돌게 됩니다.
그래서 controller에서는 Entity를 반환해서는 안됩니다. 위와같은 무한루프가 생길 수 있고 Entity는 언제든 바뀔 수 가있습니다. 컬럼이 추가된다든지의 이유로 바뀌는순간 api의 스펙이 바뀌게 되므로 어제까지만 해도 api를 잘 가져다 쓰고 있는 입장에서 api스펙이 바뀌게 되므로 이게 예를들어 만명의 사용자가 쓰는 api스펙이 바뀌어버리면 만명이 그 스펙에 맞게 코드를 바꿔야하는 불상사가 생기게 됩니다...그래서 controller에서는 Entity에 값을 넘겨주는 DTO를 생성해서 그 DTO를 반환해주어야 합니다.
회원가입을 한다고 치면 아이디와 패스워드 가입한 날짜 컬럼이있는 Member 엔티티가 있으면 컨트롤러에서 아이디와 패스워드를 받아서 바로 엔티티로 가는게 아니라 Member Form이라는 DTO를 만들어 아이디와 패스워드만 넘기는 식으로 해야 됩니다. 그리고 DTO를 다시 response해주는것입니다.
양방향 매핑 정리
단방향 매핑으로 설계를 끝내야 한다.
실무에서는 테이블만 해도 몇십 개가 되는데 객체만으로 이걸 풀 수 있냐라고 하면 아니라고 단호하게 대답할 수 있습니다. 테이블 설계를 머리로 그리면서 객체 설계를 계속해야 됩니다.
그 시점에 테이블 관계에서 대략적인 포링 키가 다 나옵니다. 1대다(1대다) 등 N 쪽에 다 단방향 매핑을 걸어서 들어가야 되는데 이때 단방향 매핑으로 일단 설계를 다 끝내야 합니다.
여기서 양방향 매핑을 배웠는데 왜 단방향으로 끝내라고 하면 양방향 매핑은 반대사이드에 조회 기능이 추가되는 것입니다.
뭔가 팀 엔티티에서 멤버들을 알고 싶으면 이럴 때 양방향 매핑으로 mappedBy를 해주는 것입니다.
그러니깐 jpa에서의 설계는 사실 단방향 매핑에서 다 끝이 난다고 말할 수 있는 것입니다.
실무에서는 역방향으로 참조할일이 너무 많습니다.
하지만 초기 설계단계에서 위에서 말한거와 같이 단방향매핑만으로 이미 설계를 끝낼 수 있습니다.
단방향매핑만 잘 해놓은 상태라면 양방향 매핑을 그냥 코드 몇줄만 넣으면 끝이 납니다.
테이블은 하나도 안건드리고 Team엔티티에 List만 넣어주면 끝이나는 것과 같이 단방향 매핑이 얼마나 중요한지 알 수 있습니다...!
연관관계의 주인을 정하는 기준
- 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
- 연관관계의 주인은 외래키의 위치를 기준으로 정해야함
인프런 강의 참고
'JPA' 카테고리의 다른 글
[JPA] 고급매핑 (0) | 2022.06.18 |
---|---|
[JPA] 다양한 연관 관계 매핑 (0) | 2022.06.18 |
[JPA] 양방향 연관관계와 연관관계의 주인 (0) | 2022.06.15 |
[JPA] 필드와 컬럼 매핑 (0) | 2022.06.14 |
[JPA] 플러시란? (0) | 2022.06.13 |
댓글