01. 양방향 매핑
양방향 매핑
양방향으로 참조할 수 있는 것을 양방향 매핑이라고 한다.
테이블 연관 관계에서는, Team 입장에서 속한 Member를 알려면 Team의 TEAM_ID와 Member의 TEAM_ID를 조인하면 된다. 반대로 Member 입장에서 속한 Team을 알려면 Member의 TEAM_ID와 Team의 TEAM_ID를 조인하면 된다. 결국 같은 말이다.
즉 테이블 연관관계에서는, 외래키 하나로 양방향이 존재하는 것이다. 하지만 객체 연관관계의 경우에는 그렇지 않다.
양방향 객체 연관관계
양방향 객체 연관관계를 그림으로 나타내면 다음과 같다.
이를 코드로 나타내면 다음과 같다. Member 엔티티는 단방향과 동일하고, Team 엔티티만 Member를 List 형식으로 추가하면 된다.
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
복습 차원에서 Member 클래스에 대한 정리를 하면 다음과 같다.
@Entity
로 Member 클래스가 DB 테이블에 대응하는 하나의 클래스임을 명시한다. @Id
로 PK를 명시한다. @GeneratedValue
로 기본키 값에 대한 생성 전략을 명시한다. @Column
의 name 속성을 통해 실제 DB에서 사용할 컬럼 이름을 지정한다. Member 클래스에 @ManyToOne
으로 Member가 다, Team이 일 임을 명시한다. @JoinColumn
의 name 속성을 통해 어떤 컬럼과 조인을 수행할 것인지 명시한다.
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
List members = new ArrayList();
}
복습 차원에서 Team 클래스에 대한 정리를 하면 다음과 같다.
@Entity
로 Team 클래스가 DB 테이블에 대응하는 하나의 클래스임을 명시한다.@Id
로 PK를 명시한다.@GeneratedValue
로 기본키 값에 대한 생성 전략을 명시한다.@OneToMany
로 Team이 일, Member가 다 임을 명시한다.@OneToMany
의 mappedBy
를 통해 매핑의 주인을 명시한다.
관례상 List는 new로 생성한다. 그래야 null pointer exception이 발생하지 않는다.
양방향 매핑을 사용하는 코드
참조를 이용하여 반대 방향으로 객체 그래프를 탐색할 수 있다. JpaMain 클래스의 try문 안에 다음과 같이 작성할 수 있다.
Team findTeam = em.find(Team.class, team.getId());
int memberSize = findTeam.getMembers().size(); // 역방향으로 조회할 수 있다.
Member findMember = em.find(Member.class, member.getId());
List members = findMember.getTeam().getMembers(); // member에서 team으로, team에서 member로
for(Member m : members) {
System.out.println("m = " + m.getUsername());
}
02. 연관관계의 주인과 mappedBy
객체와 테이블이 관계를 맺는 차이
객체 연관관계는 2개이다. 회원에서 팀으로 가는 단방향 연관관계 1개, 팀에서 회원으로 가는 단방향 연관관계 1개인 셈이다.
반면, 테이블 연관관계는 1개이다. 회원과 팀간의 양방향 연관관계 1개이다.
객체의 양방향 관계
객체의 양방향 관계는 서로 다른 단방향 관계 2개이다. 따라서 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
class A {
B b;
}
class B {
A a;
}
테이블의 양방향 연관관계
테이블은 외래 키 하나로 두 테이블의 연관관계를 관리한다.
MEMBER.TEAM_ID 혹은 TEAM.TEAM_ID로 양방향 연관관계를 가지기 때문에 양쪽으로 조인할 수 있다.
둘 중 하나로 외래 키를 관리해야 한다.
객체 연관관계에서는, Member에서 Team으로 가는 team 참조와 Team에서 Member로 가는 members 참조가 있다. 값을 바꾸고 싶을 경우, Member의 team을 바꿔야 하나, Team의 members를 바꿔야 하나? 둘 중 무엇으로 매핑을 해야하나? DB 입장에서는 참조는 어떻게 하든, TEAM_ID 값만 update 되면 된다.
연관관계의 주인(Owner)
연관관계의 주인을 정하는 규칙을 양방향 매핑 규칙이라고 한다. 객체의 두 관계 중 하나를 연관관계의 주인으로 지정하는 것이다. 연관관계의 주인만이 외래 키를 관리할 수 있다. 주인이 아닌 쪽은 읽기만 가능하다. 주인이 아닌 쪽에서 mappedBy 속성으로 주인을 지정해야 한다.
누구를 주인으로 정해야하나?
외래 키가 있는 곳을 주인으로 정하면 된다. 즉, 여기서는 MEMBER가 FK로 TEAM_ID를 가지므로 MEMBER 테이블을 주인으로 결정하는 것이다.
03. 양방향 매핑 시 주의할 점
연관관계의 주인에 값을 입력하기
연관관계의 주인에 값을 입력하지 않는 경우가 많다. 연관관계의 주인은 Member인데 다음과 같이 Team에 값을 입력하게 된다면 데이터가 정상적으로 들어가지 않는다. 다음은 예시 코드이다.
Member member = new Member();
member.setUsername("member1");
em.persist(member);
Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member); // 연관관계의 주인이 아닌 객체에 값을 입력한 경우이다.
em.persist(team);
em.flush();
em.clear();
tx.commit();
실행 결과를 보자. insert 쿼리문은 분명 두 번 나가는데, DB를 확인해보면 MEMBER 테이블의 TEAM_ID에 null 들어가있는 것을 볼 수 있다.
주인인 MEMBER 에 작성하면 정상적으로 데이터가 들어간다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // member가 주인이다.
em.persist(member);
em.flush();
em.clear();
tx.commit();
JPA 입장에서 보면 위의 코드가 맞긴 하다. 하지만 객체지향적으로 생각해본다면 항상 양쪽 다 값을 입력해야 하므로 다음 코드도 포함하는 것이 옳다.
team.getMembers().add(member);
연관관계 편의 메소드
하지만, Member의 setTeam 메서드에 다음 코드를 추가하면 위의 저 코드는 필요가 없다. 이것은 메서드를 원자적으로 사용하는 것이다. 이것을 연관관계 편의 메소드라고 부르도록 하자. 관례 때문에 setTeam 을 changeTeam으로 메소드 이름 바꾸고, 기본 setTeam 메소드를 추가하자.
team.getMembers().add(this); // 나 자신의 인스턴스를 넣어준다
양방향 매핑시에 무한 루프를 조심하자
toString(), lombok, JSON 생성 라이브러리를 이용할 때 참조가 반복되므로 주의해야한다.
다음은 toString()을 이용할 때 오류이다. Team의 toString, Member의 toString() 을 생성하여 프로젝트를 실행하면 StackOverflow Error가 나타난다.
실제 스프링 프로젝트로 애플리케이션 개발을 진행할 때, Controller에서는 Entity로 반환하지 말고, DTO로 반환하자. 무한루프 이슈가 발생할 수 있고, Entity 변경하는 순간, API 스펙에 문제가 생길 수 있기 때문이다.
04. 양방향 매핑 정리
양방향 매핑에 대해 정리하면 다음과 같다.
단방향 매핑만으로도 이미 연관관계 매핑은 완료된 것이다. 양방향 매핑은 반대 방향으로 조회 기능이 추가된 것 뿐이다. JPQL에서 역방향으로 탐색할 일이 많다. 단방향 매핑을 잘 하고 애플리케이션 개발을 본격적으로 시작할 때 양방향이 필요하다면 추가해도 된다. 양방향 매핑은 테이블에 영향을 주지는 않는다. 양방향 연관관계에서 연관관계의 주인을 정하는 기준을 정리하면 다음과 같다. 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안된다. 연관관계의 주인은 외래 키의 위치를 기준으로 정해야 한다.
'JPA' 카테고리의 다른 글
상속관계 매핑 (0) | 2024.01.08 |
---|---|
다양한 연관관계 매핑 (0) | 2024.01.07 |
단방향 연관관계 (2) | 2024.01.06 |
필드와 컬럼 매핑 (0) | 2024.01.06 |
데이터베이스 스키마 자동 생성 (0) | 2024.01.05 |