@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
// @Column(name = "TEAM_ID")
// private Long teamId;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
…
위 코드에서 주석친 것 처럼 외래키를 사용하면 객체지향적이지 못함
객체지향적으로 외래키를 맵핑하려면 해당 객체에 @ManyToOne과 같은 어노테이션을 설정
@ManyToOne은 다대일 관계로 해당 어노테이션을 선언한 클래스가 "다"가 됨
즉, 멤버가 "다"고 팀이 "일"이 됨.
또한, 대상 클래스에서 어떤 키에 맵핑될지를 정의해야 해서 @JoinColumn으로 해당 필드 값을 선언
연관관계 저장, 조회, 수정 방법
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); //단방향 연관관계 설정, 참조 저장
em.persist(member);
//조회
Member findMember = em.find(Member.class, member.getId());
//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();
// 새로운 팀B
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
// 회원1에 새로운 팀B 설정
member.setTeam(teamB);
연관관계 양방향 맵핑 방법
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
…
}
연관관계의 주인은 Team이 아니라 Member이므로 Team에서 mappedBy로 Member의 외래키인 "member"를 지정
양방향 맵핑시 상대방 값 입력 방법
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);
em.persist(member);
위의 코드는 Team, Member가 서로 양방향 맵핑 관계인데, Team에서만 member를 추가한 상황
하지만, 이전에 설명했듯이 연관관계의 주인만이 외래 키를 관리함
따라서, 값 추가를 주인인 Member가 아닌 Team에서 하면 반영이 되지 않음.
위와 같은 예시는 양방향 맵핑에서 가장 많이 하는 실수
해결 방법: 항상 양쪽에 값을 설정하자
항상 양쪽에 값을 설정하면 값이 입력이 되지 않는 문제를 방지할 수 있음
또한, 영속성 컨텍스트때문에 값 입력이 제대로 되지 않는 문제도 해결할 수 있음
연관관계 편의 메서드
...
public void setTeam(Team team){
this.team = team;
}
public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
양방향 맵핑시 양쪽에 모두 값 설정을 하도록 하기 위해서는 한쪽에서 값 등록을 할 때 상대방 쪽도 등록해주면 됨
위와 같이 changeTeam 메서드에서 Member의 team 필드를 세팅해줄 때 팀의 멤버에도 자신(member)를 넣어줌
이 때, 기존 setTeam은 setter 메서드이기 때문에 setter는 고유의 동작만 하도록 놔두고 changeTeam과 같은 새로운 메서드를 써주는게 좋음
이는 Member에서 해주지 않고 Team에서 해줘도 상관은 없긴 함.
양방향 맵핑시 무한 루프 주의
롬복의 toString() 함수가 구현되어 있을 때 Member 객체의 toString() 함수를 부른다면 내부 team을 toString()하게 될 것이고, team은 또 내부의 member의 toString()을 부르고 이게 무한 반복됨
따라서, 양방향 맵핑시에는 lombok의 toString()은 사용되지 않도록 꼭 조심해야함
또한, Controller에서 Response 할 때 Entity로 Response를 하게 되면 자동으로 json으로 변환이 되는데, 이러한 json 변환시에도 Member의 Team이 json화 되면서 Team의 Member가 또 json되고 이러한 문제가 발생할 수 있음.
따라서, 이러한 json 문제를 조심해야하며 Controller에서 Response시에는 Entity가 아닌 DTO를 사용해야 함
양방향 맵핑이 필요한가?
양방향 맵핑은 여러가지 문제점들을 가지고 있음.
따라서 왠만하면 단방향 맵핑으로 조회를 해야 함
실제로, 객체의 관계에서나 양방향 맵핑이 있지 DB 테이블에는 서로를 담고 있는 양방향 맵핑 관계는 없음.
즉, 서로를 연관지으로면 단방향 맵핑만으로도 충분하다는 말
따라서, 기본적으로는 단방향 맵핑을 잘 해주고 추후에 정말 필요한 순간에 양방향 맵핑을 그 때 추가해줘도 됨.