1. 연관관계 매핑할 때 고려해야 할 것
1) 다중성
다중성 종류
- 다대일
- 일대다
- 일대일
- 다대다
2) 단방향, 양방향
두 엔티티 중 한쪽만 참조하는 단방향 관계인지, 서로 참조하는 양방향 관계인지
3) 연관관계의 주인
양방향 관계면 연관관계의 주인을 정해야 한다
전제 조건: 다중성은 왼쪽을 연관관계의 주인으로 정했다. 예를 들어 일대다면 일 쪽이 연관관계의 주인인 것.
2. 다대일
1) 다대일 단방향
Member(다) | Team(일) |
id | id |
Team team | name |
username |
MEMBER | TEAM |
MEMBER_ID(PK) | TEAM_ID(PK) |
TEAM_ID(FK) | NAME |
USERNAME |
@Entity
public class Member{
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class Team{
@Id
@GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
}
2) 다대일 양방향
Member | Team |
id | id |
Team team | List member |
username | name |
MEMBER | TEAM |
MEMBER_ID(PK) | TEAM_ID(PK) |
TEAM_ID(FK) | NAME |
USERNAME |
@Entity
public class Member{
@Id
@GeneratedValue
@Column(name = "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);
}
}
}
특징
- 양방향은 외래 키가 있는 쪽이 연관관계의 주인이다
- 양방향 연관관계는 항상 서로를 참조한다
- 연관관계 편의 메소드(addMember(), setTeam())를 사용
3. 일대다
1) 일대다 단방향
Team | Member |
id | id |
name | username |
List members |
TEAM | MEMBER |
TEAM_ID(PK) | MEMBER_ID(PK) |
NAME | TEAM_ID(FK) |
USERNAME |
@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;
}
특징
- 엔티티를 하나 이상 사용할 수 있으므로 자바 컬렉션 중 하나를 사용해야 한다
- 반대쪽 테이블에 있는 외래 키를 관리한다
- 일대다 관계에서 외래 키는 항상 다쪽 테이블에 있다. 하지만 다 쪽인 Memer 엔티티에는 외래 키를 매핑할 수 있는 참조 필드가 없다. 대신에 반대쪽인 Team 엔티티에만 참조 필드인 members가 있다. 따라서 반대편 테이블의 외래 키를 관리하는 특이한 모습이 나타난다.
- @JoinColumn을 무조건 명시해야 한다.
단점
- 만약 본인 테이블에 외래 키가 있으면 엔티티의 저장과 연관관계 처리를 INSERT SQL 한 번으로 끝낼 수 있지만, 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있어서 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 한다.
- 위와 같은 문제로 인해 성능 문제도 발생가능 하고, 관리도 부담스럽다.
- 그러니 일대다 단방향보다는 다대일 양방향 매핑을 사용하자
2) 일대다 양방향
Team | Member |
id | id |
name | username |
List members | Team team |
TEAM | MEMBER |
TEAM_ID(PK) | MEMBER_ID(PK) |
NAME | TEAM_ID(FK) |
USERNAME |
@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) // Team과 같은 외래키를 관리하게 되어서 읽기만 가능하게 속성 설정
private Team team;
}
특징
- 일대다 양방향 매핑은 존재하지 않는다. 대신 다대일 양방향 매핑을 사용해야 한다. 데베 특성상 일대다, 다대일 관계는 항상 다 쪽에 외래 키가 있다. 따라서, @ManyToOne에는 mappedBy 속성이 없다.
- 하지만, 일대다 단방향 매핑 반대편에 같은 외래 키를 사용하는 다대일 단방향 매핑을 읽기 전용으로 추가하면 가능하긴 하다.
- 일대다 단방향 매핑이 가지는 단점을 그대로 가진다. 그러니 될 수 있으면 다대일 양방향 매핑을 사용해야 한다.
4. 일대일
특징
- 일대일 관계의 반대도 일대일 관계다.
- 테이블 관계에서 일대다, 다대일은 항상 다쪽이 외래 키를 가진다. 반면에 일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래 키를 가질 수 있다. 따라서 주 테이블이나 대상 테이블 중에 누가 외래 키를 가질지 서택해야 한다.
1) 주 테이블에 외래 키
특징
- 주 테이블에 외래 키를 두고 대상 테이블을 참조한다.
- 외래 키를 객체 참조와 비슷하게 사용할 수 있어서 객체지향 개발자들이 선호한다.
- 주 테이블이 외래 키를 가지고 있으므로 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있다.
단방향
Member | Locker |
id | id |
Locker locker | name |
username |
MEMBER | LOCKER |
MEMBER_ID(PK) | LOCKER_ID(PK) |
LOCKER_ID(FK, UNI) | NAME |
USERNAME |
@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;
}
양방향
Member | Locker |
id | id |
Locker locker | name |
username | Member member |
MEMBER | LOCKER |
MEMBER_ID(PK) | LOCKER_ID(PK) |
LOCKER_ID(FK, UNI) | NAME |
USERNAME |
@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 엔티티에 있는 Member.locker가 연관관계의 주인이다.
2) 대상 테이블에 외래 키
특징
- 전통적인 데베 개발자들은 대상 테이블에 외래 키 두는 것을 선호한다.
- 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지할 수 있다.
단방향
일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지원하지 않고, 이런 모양으로 매핑할 수 있는 방법도 없다.
양방향
Member | Locker |
id | id |
Locker locker | name |
username | Member member |
MEMBER | LOCKER |
MEMBER_ID(PK) | LOCKER_ID(PK) |
USERNAME | NAME |
MEMBER_ID(FK, UNI) |
@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;
}
- 일대일 매핑에서 대상 테이블에 외래 키를 두고 싶으면 양방향으로 매핑한다.
- 주 엔티티인 Member 엔티티 대신에 대상 엔티티인 Locker를 연관관계의 주인으로 만들어서 LOCKER 테이블의 외래 키를 관리하도록 했다.
5. 다대다
- 관계형 데베는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다. 그래서 보통 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용한다.
- 객체는 테이블과 다르게 객체 2개로 다대다 관계를 만들 수 있다. 회원 객체는 컬렉션을 사용해서 상품들을 참조하면 되고, 상품들도 컬렉션을 사용해서 회원들을 참조하면 된다.
1) 다대다 단방향
@Entity
public class Member{
@Id
@Column(name = "member_id")
private String id;
private String username;
@ManyToMany
// @JoinTable은 연결 테이블을 만드는 어노테이션이라고 할 수 있다.
@JoinTable(name = "member_product", joinColumns = @JoinJolumn(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;
}
2) 다대다 양방향
@Entity
public class Member{
@Id
@Column(name = "member_id")
private String id;
private String username;
@ManyToMany
@JoinTable(name = "member_product", joinColumns = @JoinJolumn(name = "member_id"), inverseJoinColumns = @JoinColumn(name = "product_id"))
private List<Product> products = new ArrayList<Product>();
public void addProduct(Product product){ // 연관관계 편의 메소드
products.add(product);
products.getMembers().add(this);
}
}
@Entity
public class Product{
@Id
@Column(name = "product_id")
private String id;
private String name;
@ManyToMany(mappedBy = "products")
private List<Member> members;
}
3) 다대다 : 매핑의 한계와 극복, 연결 엔티티 사용
- @ManyToMany를 사용한 매핑을 실무에서 사용하기에는 한계가 있다. 연결 테이블에는 단순히 아이디만 담기는 것이 아니라 추가적인 컬럼이 더 필요한 경우가 많은데, 이렇게 추가하면 매핑이 힘들기 때문이다.
- 그래서 추가로 연결 테이블에 해당하는 엔티티를 만든다.
Member | MemberProduct | Product |
id | member | id |
memberProducts | product | name |
username | orderAmount | |
orderDate |
MEMBER | MEMBER_PRODUCT | PRODUCT |
MEMBER_ID(PK) | MEMBER_ID(PK, FK) | PRODUCT_ID(PK) |
USERNAME | PRODUCT_ID(PK, FK) | NAME |
ORDERAMOUNT | ||
ORDERDATE |
@Entity
public class Member{
@Id
@Column(name = "member_id")
private String id;
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts;
}
@Entity
public class Product{
@Id
@Column(name = "product_id")
private String id;
private String name;
}
@Entity
@IdClass(MemberProductId.class)
public class MemberProduct{
@Id
@ManyToOne
@JoinColumn(name = "member_id")
private Member member; //MemberProductId.member와 연결
@Id
@ManyToOne
@JoinColumn(name = "product_id")
private Product product; //MemberProductId.product와 연결
private int orderAmount;
private Date orderDate;
}
public class MemberProductId implements Serializable{
private String member; //MemberProduct.member와 연결
private String product; //MemberProduct.product와 연결
@Override
public boolean equals(Object o){
}
}
복합 기본 키
두 개의 키가 합쳐져서 기본 키를 구성하는 경우를 의미한다
식별자 클래스
- JPA에서 복합 키를 사용하려면 별도의 식별자 클래스를 만들어야 한다.
- Serializable을 구현해야 한다
- equals와 hasCode 메소드를 구현해야 한다
- 기본 생성자가 있어야 한다
- 식별자 클래스는 public이어야 한다
- @IdClass를 사용하는 방법 외에 @EmbeddedId를 사용하는 방법도 있다
식별 관계
부모 테이블의 기본 키를 받아서 자신의 기본 키 + 외래 키로 사용하는 것
비식별 관계(이걸 더 추천)
부모 테이블의 기본 키를 단순히 외래 키로만 사용하는 것
4) 다대다 : 새로운 기본 키 사용
복합키를 사용하지 않고 데베에서 자동으로 생성해주는 대리 키를 Long값으로 사용한다
- 간편하다
- 영구히 쓸 수 있다
- 비즈니스에 의존하지 않는다
- ORM매핑 시에 복합 키를 만들지 않아도 되므로 간단히 매핑을 완성할 수 있다.
MEMBER | ORDERS | PRODUCT |
MEMBER_ID(PK) | ORDER_ID(PK) | PRODUCT_ID(PK) |
USERNAME | MEMBER_ID(FK) | NAME |
PRODUCT_ID(FK) | ||
ORDERAMOUNT | ||
ORDERDATE |
@Entity
public class Order{
@Id
@GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;
private int orderAmount;
}
@Entity
public class Member{
@Id
@Column(name = "member_id")
private String id;
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts;
}
@Entity
public class Product{
@Id
@Column(name = "product_id")
private String id;
private String name;
}
'Spring > jpa' 카테고리의 다른 글
[JPA] 고급 매핑 관련 어노테이션 (0) | 2023.02.16 |
---|---|
[JPA] 상속 관계 매핑, MappedSuperClass (0) | 2023.02.16 |
[JPA] 연관관계 어노테이션 (0) | 2023.02.13 |
[JPA] 단방향 연관관계, 양방향 연관관계 (0) | 2023.02.13 |
[JPA] 기본 키 매핑 (0) | 2023.02.10 |