연관관계 매핑 1편에서는 연관관계 매핑이 필요한 이유와 구현하는 방법에 대해 알아보았다. 1편에서 이미 예시를 통해 다대일, 일대다 연관관계는 살펴보았으므로 2편에서는 일대일(1:1), 다대다(N:N) 연관관계에 대해 자세히 알아보자.
일대일(1:1) 연관관계
일대일 관계는 하나의 객체가 다른 하나의 객체만을 참조할 수 있고, 반대 방향으로도 마찬가지인 연관관계이다. 예를 들어 다음과 같은 상황이다.
- 한명의 학생은 하나의 사물함만을 가질 수 있다.
- 하나의 사물함은 한명의 학생 것이다.
이런 경우, 학생과 사물함은 일대일 연관관계라고 할 수 있다.
일대일 연관관계의 주인
일대다 연관관계의 경우, '다'쪽인 외래키(foreign key)를 가지고 연관관계의 주인이 되었다. 하지만 일대일 관계는 어느쪽이 연관관계의 주인이 되도 상관이 없다.
보통 일대일 연관관계도 두 테이블이 주테이블과 대상테이블로 구분 되는데 위 예시에서는 학생이 주 테이블, 사물함이 대상 테이블이 된다(학생이 사물함을 소유한다는 관점) 이때 주 테이블이 연관관계의 주인인 경우와 대상 테이블이 연관관계의 주인인 경우 다음과 같은 관점 차이가 발생한다.
- 주 테이블이 연관관계의 주인
- 주 테이블이 외래키를 가지고 관리한다. 그렇게 되면 외래키를 마치 대상 테이블에 접근할때 객체 참조처럼 사용할 수 있다. 이 방법의 장점은 주 테이블에만 접근해도 외래키를 통해 연관된 대상 테이블까지 쉽게 접근할 수 있다. 보통 객체지향 관점에서 선호하는 방식이다.
- 대상 테이블이 연관관계의 주인
- 전통적인 데이터베이스에서처럼 대상 테이블이 외래키를 관리하게 된다. 그렇게 되면 만약 주테이블과 대상 테이블이 일대다(1:N)관계가 되어도 구조의 변경없이 사용할 수 있다. 보통 데이터베이스의 관점에서 선호하는 방식이다.
이제 코트를 통해 살펴보자
주 테이블이 연관관계의 주인
1. 단방향
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne
@JoinColumn(name="LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private int lockernum;
}
@JoinColumn 애노테이션을 통해 외래키를 지정해준다.
2. 양방향
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne
@JoinColumn(name="LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private int lockernum;
@OneToOne(mappedBy="locker")
private Member member;
}
mappedBy 속성을 통해 Locker가 연과관계의 주인이 아님을 선언하고, 회원 객체를 참조한다.
대상 테이블이 연관관계의 주인
1. 단방향
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private int lockernum;
@OneToOne
@JoinColumn(name="MEMBER_ID")
private Member member;
}
@JoinColumn을 연관관계의 주인인 사물함 테이블에 지정해준다.
2. 양방향
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne(mappedBy='member')
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private int lockernum;
@OneToOne
@JoinColumn(name="MEMBER_ID")
private Member member;
}
다대다(N:N) 연관관계
다대다 연관관계는 두 엔티티가 서로 여러개를 참조할 수 있는 관계를 말한다. 아래 예시를 보자.
- 한명의 회원은 여러 모임에 들어갈 수 있다.
- 하나의 모임에는 여러명의 회원이 소속되어 있다.
위의 예시에서는 회원이 여러개의 모임을 참조할 수 있고, 반대로 모임도 여러개의 회원을 참조할 수 있다. 즉, 회원과 모임은 다대다(N:N) 연관관계에 있는것이다.
다대다(N:N) => 일대다(1:N) - 다대일(N:1)
다대다 관계는 데이터베이스의 테이블로는 구현할 수 없다. 그렇기 때문에 연결테이블을 추가하여 다대다 관계를 일대다-다대일 관계로 풀어서 사용하게 된다. 아래 다이어그램을 보자.
회원테이블과 팀테이블 사이에 둘을 이어주는 연결 테이블 MEMBER_TEAM을 추가한다. MEMBER_TEAM 테이블은 회원과 팀 데이터를 쌍으로 가지고 있어 어떤 회원이 어떤 팀에 있는지를 저장하고 있다.
이제 코드로 구현해보자.
구현
단방향
@Entity
public class Member {
@Id @Column(name='MEMBER_ID')
private Long id;
private string username;
@ManyToMany
@JoinTable(
name = "MEMBER_TEAM",
joinColumns = @JoinColumn(name="MEMBER_ID"),
inverseJoinColumns = @Joincolumn(name="TEAM_ID")
)
private List<Team> teams = new ArrayList<>();
}
@Entity
public class Team {
@Id @Column(name="TEAM_ID")
private Long id;
private String teamname;
...
}
회원만 팀을 참조하는 단방향 관계이다. 일단 다대다 관계를 나타내는 @ManyToMany 애노테이션을 붙여준다. 그리고 @JoinTable 애노테이션을 통해 연결 테이블을 매핑한다.
양방향
@Entity
public class Member {
@Id @Column(name='MEMBER_ID')
private Long id;
private string username;
@ManyToMany
@JoinTable(
name = "MEMBER_TEAM",
joinColumns = @JoinColumn(name="MEMBER_ID"),
inverseJoinColumns = @Joincolumn(name="TEAM_ID")
)
private List<Team> teams = new ArrayList<>();
}
@Entity
public class Team {
@Id @Column(name="TEAM_ID")
private Long id;
private String teamname;
@ManyToMany(mappedBy='team')
private List<Member> members = new ArrayList<>();
}
양방향 관계의 경우 단뱡향 관계의 코드에서 Team에 회원리스트 참조만 넣어주면 된다. 이때 @ManyToMany 애노테이션을 통해 다대다관계를 지정하고, mappedBy 속성을 통해 연관관계의 주인을 설정한다.
연결테이블의 한계, 연결 엔티티
@JoinTable을 통해 구현한 연결 테이블에는 아주 큰 한계점이 있다. 바로 설정한 키 값외에 다른 칼럼을 추가할 수 없다는 것이다.
아래의 다이어그램을 보자.
기존의 연결 테이블에 추가적으로 역할(ROLE)과 가입일(JOINDATE) 칼럼이 추가되었다. @JoinTable 애노테이션을 통해서는 위의 테이블을 매핑할 수 없다. 그렇기 때문에 개발자가 직접 엔티티를 작성하여 매핑해야 한다. 아래의 코드를 보자
@Entity
public class Member {
@Id @Column(name='MEMBER_ID')
private Long id;
private string username;
@OneToMany(mappedBy="member")
private List<MemberTeam> memberTeams = new ArrayList<>();
}
@Entity
public class Team {
@Id @Column(name="TEAM_ID")
private Long id;
private String teamname;
@OneToMany(mappedBy='team')
private List<MemberTeam> memberTeams = new ArrayList<>();
}
회원 테이블과 팀 테이블 모두 연결테이블과 일대다 관계로 매핑되게 된다. 그러므로 @OneToMany 애노테이션을 지정하게된다.
@Entity
public class MemberTeam {
@Id @Column(name="MEMBER_TEAM_ID")
private Long id;
@ManyToOne
@JoinColumn(name="MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
private String role;
private Date joinDate;
}
MemberTeam은 연결 엔티티이다. 회원, 팀과 각각 다대일 관계를 갖는다. 엔티티를 직접 구현함으로써 키 값 외에도 다른 데이터를 가질 수 있다.
추가) 일대일/다대다 관계모두 연관관계 편의 메서드를 구현해놓는것을 추천한다(1편참고)
마무리
두편에 걸져 JPA가 제공하는 연관관계 매핑에 대해 알아보았다.
'JPA' 카테고리의 다른 글
[JPA] 지연 로딩 (Lazy Loading) (0) | 2022.08.13 |
---|---|
[JPA] 연관관계 매핑 1 (0) | 2022.08.02 |
[JPA] 엔티티(Entity) 매핑 : 기본키(Primary Key) 매핑하기 (0) | 2022.06.23 |
[JPA] 엔티티 매핑 (0) | 2022.06.22 |
[JPA] SQL을 직접 다룰 때의 문제점과 ORM 프레임워크 JPA의 등장 (0) | 2022.06.14 |