엔티티의 연관관계를 매핑할 때, 고려해야 할 3가지
1. 다중성 : 다대일, 일대다, 일대일, 다대다
2. 단방향, 양방향
3. 연관관계의 주인
이를 고려하여 모든 연관관계에 대해 자세히 알아볼 것이다.
데이터베이스 테이블의 일대다, 다대일 관계에서 외래 키는 항상 다쪽에 있다.
따라서 객체 양방향 관계에서 연관관계의 주인은 항상 다쪽이다.
다음 코드를 통해 다대일 단방향 연관관계를 알아보자.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
위 코드를 살펴보면
회원은 Member.team으로 팀 엔티티를 참조할 수 있지만, 팀에는 회원을 참조하는 필드가 없다.
또한 @JoinColumn(name = "TEAM_ID")를 사용하여 Member.team 필드를 TEAM_ID 외래 키와 매핑했으므로, Member.team 필드로 회원 테이블의 TEAM_ID 외래키를 관리한다.
💡@Id @GeneratedValue : id 필드가 이 엔티티의 PK이고, PK의 값을 자동으로 생성한다.
다음 코드를 통해 다대일 양방향 관계를 알아보자.
// 회원 엔티티
@Entity
public void Member{
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
public void setTeam(Team team){
this.team = team;
// 무한루프에 빠지지 않도록 체크
if(!team.getMembers().contains(this)){
team.getMembers().add(this);
}
}
}
// 팀 엔티티
@Entity
public class Team{
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
public void addMember(Member member){
this.members.add(member);
// 무한루프에 빠지지 않도록 체크
if(member.getTeam() != this){
member.setTeam(this);
}
}
}
위 코드를 살펴보면
Member.team은 연관관계의 주인이고, Team.members은 연관관계의 주인이 아니다.
일대다 관계는 엔티티를 하나 이상 참조할 수 있으므로 자바 컬렉션인 Collection, List, Set, Map 중에 하나를 사용해야 한다.
다음은 일대다 단방향으로 매핑한 팀 엔티티와 회원 엔티티 코드이다.
// 팀 엔티티
@Entity
public class Team{
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID") // Member테이블의 TEAM_ID (FK)
private List<Member> members = new ArrayList<Member>();
}
// 회원 엔티티
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
}
💡일대다 단방향 관계를 매핑할 때는 @JoinColumn을 명시해야 한다.
-> 그렇지 않으면, JPA는 연결 테이블을 중간에 두고 연관관계를 관리하는 조인 테이블 전략을 기본으로 사용해서 매핑한다.
일대다 양방향 매핑은 존재하지 않으며, 다대일 양방향 매핑을 사용해야 한다.
일대다 양방향 매핑을 하고자 한다면, 일대다 단방향 매핑 반대편에 같은 외래 키를 사용하는 다대일 단방향 매핑을 읽기 전용으로 하나 추가하면 된다.
다음은 일대다 양방향 관계를 알아보는 팀 엔티티, 회원 엔티티 코드이다.
// 팀 엔티티
@Entity
public class Team{
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<Member>();
}
// 회원 엔티티
@Entity
public class Member{
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
private Team team;
}
위 코드는 일대다 단방향 매핑 반대편에 다대일 단방향 매핑을 추가한 것이다.
단방향
회원과 사물함의 일대일 단방향 관계를 알아보자
@Entity
public class Member {
@Id @GeneratedValue
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 String name;
}
양방향
반대 방향을 추가해서 일대일 양방향 관계를 알아보자.
@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 String name;
@OneToOne(mappedBy = "locker")
private Member member;
}
양방향 연관관계이므로 연관관계의 주인을 정해야 한다.
위 예제에서는 MEMBER 테이블이 외래 키를 가지고 있으므로 Member.locker가 연관관계의 주인이다.
@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 String name;
@OneToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
} 일대일 매핑에서 대상 테이블에 외래 키를 두고자 한다면,RDBMS는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
그래서 다대다 관계를 일대다, 다대일 관계로 풀어낸다.
// 회원 엔티티
@Entity
public class Member {
@Id @Column(name = "MEMBER_ID")
private String id;
private String username;
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT", joinColumns = @JoinColumn(name = "MEMBER_ID"), inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"))
private List<Product> products = new ArrayList<Product>():
}
//상품 엔티티
@Entity
public class Product {
@Id @Column(name = "PRODUCT_ID")
private String id;
private String name;
}
위 예제를 살펴보면 @ManyToMany와 @JoinTable을 사용하여 연결 테이블을 바로 매핑하였다. 그래서 두 엔티티를 연결하는 것 없이 매핑을 완료할 수 있다.
@JoinTable의 속성
- @JoinTable.name : 연결 테이블을 지정
- @JoinTable.joinColumns : 현재 방향인 엔티티와 매핑할 조인 칼럼 정보를 지정
- @JoinTable.inverseJoinColumns : 반대 방향인 엔티티와 매핑할 조인 칼럼 정보를 지정
JPA에서 복합 키를 사용하려면 별도의 식별자 클래스로 만들어야 한다.
다음은 복합 키를 위한 식별자 클래스의 특징이다.
연관관계 매핑 시 고려사항
다대일
일대다
다대다
연관관계 주인
식별 관계
비식별 관계