JPA를 활용해 연관 관계를 설정할 때는 "방향"을 지정해줄 수 있다.
예시로 팀과 선수를 생각해보자.
팀은 1개일 때 해당 팀에 소속된 선수는 다수 있을 것이므로 팀과 선수는 1:N 관계라고 생각할 수 있다.
여기에서 단방향을 설명하기 전에 양방향을 먼저 설명하자면, 사실 양방향은 따로 양방향이라는 개념이 존재하는 것이 아니라 "단방향 연관관계 2개를 설정한 것"이라고 생각하면 된다.
자 머리가 점점 아파온다. 이제 예시를 통해 조금씩 풀어보자.
먼저 나는 팀 데이터를 확인할 때는 선수를 보고 싶지만, 굳이 선수를 확인할 때는 해당 선수가 속한 팀 정보까지는 알고 싶지 않을 수도 있다.
내가 "최정"이라는 선수의 정보를 보고 싶은데, 굳이 SSG 팀의 연봉이나 승수, 관객 총 수를 알고 싶지는 않을 수 있다.
이런 경우 나는 연관 관계를 단방향으로 팀 → 선수로 설정하는 것이다. 이 경우 SSG라는 팀을 검색하면 선수 데이터도 같이 불러오기 때문에 여러 선수 데이터 중 "최정"이라는 이름을 가진 선수를 찾으면 되는 것이다.
반대로 나는 SSG라는 팀을 검색하고 싶은데, 굳이 SSG의 팀원에 대한 상세 정보까지는 알고 싶지 않을 수 있다. 하지만 선수 정보를 검색하면 해당 팀에 대한 정보가 나와서 팀의 성적까지 알고 싶을 수도 있다.
이 경우는 반대로 선수 → 팀으로 방향을 설정하여 "최정"이라는 선수를 검색하면 SSG 팀에 대한 정보를 볼 수 있지만, SSG 팀을 검색하면 팀에 속한 어떤 선수들에 대한 정보도 가져오지 않는 방식인 것이다.
다대일(N:1) 관계일 때 활용하는 어노테이션이다.
다대일이기 때문에 FK는 해당 Entity와 연동된 table에 FK 값이 저장될 것이다.
@ManyToOne은 @JoinColumn 어노테이션과 많이 활용된다.
위에서 말했듯 FK는 해당 Entity와 연동된 Table을 의미하기 때문에 FK가 저장될 Column Name을 지정해줄 때는 @Column으로만 입력하기에는 조금 부족한 점이 있다.
이 @JoinColumn은 "FK가 저장되는 Column이다"라는 특수한 의미를 나타내며, 멤버 변수가 기본 자료형이 아닌 연동될 Entity라는 특징이 존재한다.
만약 @JoinColumn을 생략한다면 기본적으로 "필드명 + _ + FK Column Name"으로 설정된다.
일대다(N:1) 관계일 때 활용되는 어노테이션이다.
먼저 일대다 같은 경우 어차피 FK는 외부 Table에 저장될 것이므로 굳이 연결될 Column Name을 지정하지 않아도 된다. 따라서 오로지 @OneToMany만 활용하면 된다.
일대다라는 것은 결국 해당 Entity에 대한 정보를 가지고 올 때 연동된 Table에 가져올 데이터가 많다는 것을 의미한다.
데이터가 많다? 당연히 자바에서는 이 데이터를 List 형식을 통해 받아오게 된다.
방식은 아래와 같다.
@OneToMany
private List<Member> members; // 제너릭 사용
@OneToMany(targetEntity=Member.class) // 거의 사용 안함
private List members; // 타입 알 수 없음.
양방향 매핑일 경우 연관관계의 주인이 중요해진다. 연관관계의 주인이란 "두 객체 연관관계 중 테이블의 외래키를 관리하는 주체"이다.
결론만 말하자면 항상 "다(N)"쪽이 외래키를 가지기 때문에 @ManyToOne이 주인이 된다.
사실 생각해보자면 당연한 게, @ManyToToOne Table 쪽에 FK값이 저장되기 때문에 무조건 다(N) 쪽에 FK가 저장되는 것이 당연하다.
여기서 중요한 점은 주인이 아닌 반대편은 읽기만 가능하며 외래 키를 변경하지 못한다는 것이다.
예를 들어 팀-멤버라는 관계가 있는데 멤버가 팀을 변경했을 경우 팀에서 멤버를 뽑아낸 이후 FK 값을 바꾸는 것은 수행할 수 없고 멤버를 검색하여 멤버에서 직접 PK 값을 변경해야 한다는 것이다.
주인이 아닌 양방향 관계의 객체 변수는 mappedBy 속성을 활용해 연관관계의 주인을 지정해줘야 한다. 즉, 주인은 mappedBy 속성이 없다는 의미로도 이해할 수 있을 것이다.
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
//연관관계 설정
public void setTeam(Team team) {
this.team = team;
}
//Getter, Setter
}
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
//추가
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
// Getter, Setter ...
}
가장 중요한 것은 @OneToMany의 "mappedBy" 속성이 아닐까 싶다.
mappedBy를 반대쪽 Entity인 Member의 멤버 변수 명인 "team"으로 지정하여 이 연관관계의 주인이 Member라는 것을 명시해준다.
public void testSaveNonOwner() {
//회원1 저장
Member member1 = new Member("member1", "회원1");
em.persist(member1);
//회원2 저장
Member member2 = new Member("member2", "회원2");
em.persist(member2);
Team team1 = new Team("team1", "팀1");
//주인이 아닌 곳에 연관관계 설정
team1.getMembers().add(member1);
team2.getMembers().add(member2);
em.persist(team1);
}
이 결과를 DB에서 조회하면 아래와 같은 결과가 나온다
member1 | 회원1 | null |
member2 | 회원2 | null |
다시 한번 되씹자. "연관관계 주인만이 외래 키 값을 변경할 수 있다!"
(예시로 보자면 연관관계의 주인이 될 Member만이 외래키 값을 변경할 수 있는 것이다)
좋아요공감
공유하기통계게시글 관리