엔티티 연관관계 매핑 시 고려 사항
데이터베이스 테이블의 일(1),다(N) 관계에서 외래 키는 항상 다 쪽에 있다.
따라서 객체 양방향 관계에서 연관관계 주인은 항상 다 쪽이다.
다대일 단방향[N:1]
[회원 엔티티]
@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;
...
}
다대일 양방향 [N:1, 1:N]
[회원 엔티티]
@Entity
public class 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);
}
}
...
}
다대일 관계의 반대 방향, 일대다 관계는 엔티티를 하나 이상 참조 할 수 있어서 자바 컬렉션인 Collection, List, Set, Map 중에 하나를 사용 해야 한다.
일대다 단방향 [1:N]
[팀 엔티티]
@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 을 반드시 명시 해야 한다.(그렇지 않을 시 연결 테이블을 중간에 두고 연관관계를 관리 하는 조인 테이블 전략을 기본으로 사용한다.)
일대다 단방향 매핑 단점
매핑한 객체가 관리하는 외래 키가 다른 테이블에 있음!!
본인 테이블에 외래 키가 있으면 INSERT SQL 한 번으로 끝낼 수 있지만, 다른 테이블에 외래 키가 있으면 UPDATE SQL을 추가로 실행 해야 한다. ( 다른 테이블에 외래키가 있으니 값을 모르니까)
[일대다 단방향 매핑 단점]
public void testSave() {
Member member1 = new Member("member1");
Member member2 = new Member("member2");
Team team1 = new Team("team1");
team1.getMembers().add(member1);
team1.getMembers().add(member2);
em.persist(member1); //INSERT - member1
em.persist(member2); //INSERT - member2
em.persist(team1); //INSERT-team1
//UPDATE-member1.fk
//UPDATE-member2.fk
transaction.commit();
}
//결과 SQL
insert into Member (MEMBER_ID, username) values(null,?)
insert into Member (MEMBER_ID, username) values(null,?)
insert into Team (TEAM_ID, name) values(null,?)
update Member set TEAM_ID = ? where MEMBER_ID = ?
update Member set TEAM_ID = ? where MEMBER_ID = ?
일대다 단방향 매핑보단 다대일 양방향 매핑을 사용 할 것
일대다 양방향[1:N, N:1]
양쪽이 서로 하나의 관계만 가진다.
특징
주 테이블에 외래 키
대상 테이블에 외래 키
관계형 데이터베이스의 경우 정규화된 테이블 2개로 다대다 관계를 표현할 수 없어서 중간에 연결 테이블을 두어 일대다, 다대일 관계로 풀어낼 수 있다. 하지만 객체는 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
pbulic class Product {
@Id @Column(name = "PRODUCT_ID")
private String id;
private String name;
...
}
다대다 : 양방향
@Entity
public class Product {
@Id
private String id;
@ManyToMany(mappedBy = "products") //역방향 추가
private List<Member> members;
...
}
역시나 양방향의 연관관계는 연관관계 편의메소드를 추가하여 관리하는 것이 편리 하다.
public void addProduct(Product product) {
products.add(product);
product.getMembers().add(this);
}
...
다대다: 연결 엔티티 사용
연결테이블에 컬럼이 추가 될 경우 @ManyToMany 를 사용 할 수 없다. ( 추가한 컬럼들을 매핑할 수 없기 때문) 즉, 관계형 데이터베이스처럼 중간 객체를 만들어야 한다.
@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;
}
회원상품 엔티티는 기본 키가 MEMBER_ID, PRODUCT_ID 로 구성된 복합 기본키 이다.
위 MemberProductId 클래스의 경우 MemberProductId 클래스를 구현해야 한다.
JPA 에서 복합 기본키를 사용하려면 별도의 식별자 클래스를 만들어야 하며 엔티티에 @IdClass 를 사용해 식별자 클래스를 지정하면 된다.
복합 기본키를 위한 식별자 클래스의 특징
다대다 : 새로운 기본 키 사용
추천하는 기본 키 생성 전략은 데이터베이스에서 자동으로 생성해 주는 대리 키를 Long 값으로 사용 하는 것 (장점 : 간편하고, 거의 영구히 쓸 수 있으며 비즈니스에 의존하지 않음, ORM 매핑 시 복합 키를 만들지 않아도 됨)
@Entity
public class Order {
@Id @GeneratedValue
@Column(name = "ORDER_ID")
private Long id; //Long 값의 새로운 기본키를 사용하니 보기 깔끔해짐
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
private int orderAmount;
...
}
식별자 클래스를 사용하지 않아 코드가 단순해 지는 효과가 있음
정리