보통의 RDB에서는 관계에 있어 방향을 따지지 않습니다. 관계를 맺을 때 사용하는 외래키(Foreign Key)
만으로 두 테이블은 서로 양방향으로 접근이 가능하기 때문이죠.
하지만 객체
의 경우 자신이 갖고 있지 않은 참조에 대해 접근할 수 있는 방법이 없기 때문에 @ManyToOne
, @OneToMany
등으로 관계를 매핑한다 하더라도 반대쪽 객체에서는 접근이 불가능 합니다.
간단한 시나리오를 볼께요.
Member
와 Team
두 개 테이블이 있습니다.
Member
는 한 개 Team
에 소속될 수 있고 Team
은 여러 Member
를 포함할 수 있습니다.
이를 JPA
를 이용해 엔티티로 구성해보겠습니다.
@ManyToOne
을 이용해 Member
와 Team
의 관계를 다대일로 설정하고 외래키는 다쪽인 Member
에 두었습니다.
테스트를 위해 간단한 데이터를 넣어볼께요.
Team team = new Team();
team.setTeamName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("kim");
member.setTeam(team);
em.persist(member);
tx.commit();
H2 DB를 이용해 결과를 확인해볼께요.
결과 1번은 단순히 Member
와 Team
을 조회한 결과입니다.
중요한 것은 결과 2번입니다.
결과 2번의 첫 번째 쿼리는 굉장히 자연스럽니다. 객체 입장에서도 Member
객체를 통해 Team
을 조회할 수 있죠.
하지만, 결과2의 두 번째 쿼리의 결과를 현재 Member
와 Team
객체가 만들어낼 수 있을까요?
Team
은 Member
객체를 갖고있지 않습니다. 따라서 접근할 수 있는 어떤 방법도 없습니다. 뭐 굳이 하자면 Team
의 PK를 찾아와서 해당 PK를 Member
에 질의하는 방법이 있지만 전혀 객체지향스럽지 않습니다.
우리는 Team
테이블에서 자기 팀에 소속된 전체 Member
를 조회하고 싶습니다. 이것을 위해 양방향 연관관계가 있는 것 입니다.
Team
객체에서 자기 팀에 포함된 모든 Member
를 조회하려면 List<Member>
타입의 필드를 가져야 합니다.
해당 필드를 생성하고 Team
은 여러 Member
를 포함하니 일대다 매핑을 해주도록 합니다.
@Getter @Setter
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String teamName;
@OneToMany
private List<Member> members = new ArrayList<>();
}
일단 객체 입장에서 참조 관계가 양방향이 되었으니 1차적으로 목표는 달성했지만 DB 입장을 고려하여 추가적인 설정을 반드시 필요로 합니다.
앞서 말했지만 DB는 외래키만으로 관계를 맺습니다. 바꿔말해 외래키 하나만 잘 관리되면 되는 것 입니다. 두 객체를 다시 보시죠.
현재 구조는 외래키가 양쪽에 모두 있는 것 같은 구조입니다.
외래키를 갖는 쪽 즉, 연관관계의 주인이 되는 쪽을 정해주어야 합니다.
연관관계의 주인은 1:N의 경우 N 쪽으로 해주면 됩니다.
연관관계의 주인을 설정할 때 주인을 따로 설정하는 것이 아니고 자신이 이 연관관계의 주인이 아님을 설정해줘야 합니다.
자신이 연관관계의 주인이 아닌 것을 표시하는 설정이 mappedBy
입니다.
@OneToMany(mappedBy = "team")
위와 같은 포맷이고 mappedBy
의 값은 반대쪽에 자신이 매핑되어 있는 필드명을 써주시면 됩니다. 예제의 경우 Team
자신이 Member
의 team
에 매핑되어 있으므로 team
으로 설정해준 것 입니다.
간단한 테스트를 통해 결과를 확인해볼께요.
테스트 성공 여부는 실행시 Team
의 members
에 저장한 member
가 콘솔에 찍혀야 하고 Team
테이블에는 Member
에 대한 데이터가 없어야 합니다.
tx.begin();
try{
Team team = new Team();
team.setTeamName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("kim");
member.setTeam(team);
team.getMembers().add(member);
em.persist(member);
em.flush(); em.clear();
List<Member> findMembers = team.getMembers();
for (Member findMember : findMembers) {
System.out.println("findMember.getUsername() = " + findMember.getUsername());
}
tx.commit();
결과
위 예제를 해볼 때 살짝 불편한 느낌이 드는 코드가 있었습니다.
team.getMembers().add(member);
이 부분인데요.
조회를 위한 양방향 연관관계 설정으로 직접 생성된 member를 team에 넣어주었습니다.
이렇게 양방향 연관관계에 있는 객체에 데이터를 업데이트 할 때마다 이런 식으로 하나하나 찝어서 넣어줘야 할까요?
아닙니다. 너무 실수의 여지가 많고 보다 편리한 객체 그래프 탐색을 위한 양방향 연관관계가 더 불편한 결과를 초래할 수 있습니다.
이 문제를 해결하기 위해 Member
쪽에 Team
이 세팅될 때 Team
의 List<Member>
에 동시에 값을 넣어주는 메서드를 정의합니다.
@Getter @Setter
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "name")
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
team
의 setter
메서드를 그대로 변경하여 사용해도 되지만 setter
의 관례적인 네이밍이 나중에 방해가 될 수 있으므로 되도록 의미있는 이름으로 메서드를 정의합니다.
changeTeam
에 team
이 넘어오면 일단 Member
의 team
에 저장합니다. 이때 저장되는 것은 DB에 외래키로 저장되는 것 입니다. 그리고 team
의 members
에 Member
자체를 넣어줌으로 서로 싱크를 맞추어 줍니다.
간단하게 양방향 연관관계에 대해 알아보았습니다.
양방향 연관관계는 보다 객체지향적으로 객체를 설계하기 위한 기술입니다. 이를 위해 DB에 영향이 없도록 설정을 하는 것 이지요. 바꿔 말하면 양방향 연관관계는 DB에는 영향이 없다는 것 입니다.
따라서 처음 엔티티 구조를 설계할 때는 모두 단방향 연관관계 만으로 설계가 가능합니다. 일단 단방향 연관관계로 모든 관계를 설정하고 필요할 때 객체 레벨의 양방향 연관관계를 설정하는 것이 좋습니다.
감사합니다. 😄
Janakpuri Escort agency exceeded all my expectations. The escort was not just gorgeous but also intelligent and engaging. We talked, laughed, and later explored each other’s desires without any awkwardness. It felt natural and thrilling. Perfect for anyone seeking something real and beyond the usual.
감사합니다