다대일
다대일 단방향 [N:1]
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
}
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
}
회원은 팀을 참조할 수 있지만 반대로 팀에는 회원을 참조하는 필드가 없다. 따라서 회원과 팀은 다대일 단방향 연관관계다.
다대일 양방향 [N:1, 1:N]
- 양방향은 외래키가 있는 쪽이 연관관계의 주인이다.
- 일대다와 다대일 연관관계는 항상 다(N)에 외래키가 있다.
- 양방향 연관관계는 항상 서로를 참조해야 한다.
- 항상 서로 참조하게 하려면 연관관계 편의 메소드를 작성하는 것이 좋은데 회원의 setTeam(), 팀의 addMember() 메소드가 편의 메소드들이다.
- 편의 메소드는 한 곳에만 작성하거나 양쪽 다 작성할 수 있는데, 양쪽에 다 작성하면 무한루프에 빠지므로 주의해야 한다.
@Entity
public class Member {
@Id
@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
@Column(name = "TEAM_ID")
private String 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);
}
}
}
일대다
일대다 단방향 [1:N]
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
private String 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
@Column(name = "MEMBER_ID")
private Long id;
private String username;
}
일대다 단방향 매핑의 단점
- 매핑한 객체가 관리하는 외래키가 다른 테이블에 있다는 점
- 본인 테이블에 외래키가 있으면 엔티티의 저장과 연관관계 처리를 INSERT SQL 한번만으로 끝낼 수 있지만, 다른 테이블에 외래키가 있으면 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 한다.
- Member 엔티티는 Team 엔티티를 모른다. 그리고 연관관계에 대한 정보는 Team엔티티의 members가 관리한다.
따라서 Member 엔티티를 저장할 때는 MEMBER 테이블의 TEAM_ID 외래키에 아무 값도 저장되지 않는다.
대신 Team 엔티티를 저장할 때 Team.members의 참조 값을 확인해서 회원 테이블에 있는 TEAM_ID 외래키를 업데이트한다.
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();
}
일대다 양방향 [1:N, N:1]
- 일대다 양방향과 다대일 양방향은 사실 똑같은 말이다.
- 연관관계의 주인은 항상 다 쪽인 @ManyToOne 을 사용한 곳이다.
- 일대다 단방향 매핑 반대편에 다대일 단방향 매핑을 추가했다. 이때 일대다 단방향 매핑과 같은 TEAM_ID 외래키 컬럼을 매핑했다.
이렇게 되면 둘 다 같은 키를 관리하므로 문제가 발생할 수 있다.
따라서 반대편인 다대일 쪽은 insertable = false, updatable = false로 설정해서 읽기만 가능하게 했다. - 이 방법은 일대다 양방향 매핑이라기보다는 일대다 단방향 매핑 반대편에 다대일 단방향 매핑을 읽기 전용으로 추가해서 일대다 양방향처럼 보이도록 하는 방법이다.
따라서 일대다 단방향 매핑이 가지는 단점을 그대로 가진다. - 될수 있으면 다대일 양방향 매핑을 사용하자.
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
@OneToMany
@JoinColumn(name="TEAM_ID")
private List<Member> members = new ArrayList<Member>();
}
@Entity
public class Member {
@Id
@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
@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 테이블이 외래키를 가지고 있으므로 Member 엔티티에 있는 Member.locker가 연관관계의 주인이다.
@Entity
public class Member {
@Id
@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;
}
대상 테이블에 외래키
단방향
- 일대일 관계 중 대상 테이블에 외래키가 있는 단방향 관계는 JPA에서 지원하지 않는다.
양방향
@Entity
public class Member {
@Id
@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;
}
다대다
- 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
- 중간에 연결 테이블을 추가해야 한다. (예를 들어, Member(회원)과 Product(상품)테이블을 위해 Member_Product 연결 테이블 추가)
- 객체는 테이블과 다르게 객체 2개로 다대다 관계를 만들 수 있다.
다대다:단방향
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private Long 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 Long id;
private String name;
}
- @ManyToMany와@JoinTable을 사용해서 연결 테이블을 바로 매핑 => Member_Product 엔티티 없이 매핑을 완료
- @JoinTable.name: 연결 테이블 지정
- @JoinTable.joinColumns: 현재 방향인 회원과 매핑할 조인 컬럼 정보
- @JoinTable.inverseJoinColumns: 반대 방향인 상품과 매핑할 조인 컬럼 정보
- 회원을 저장할때 연결 테이블 Member_Product 값도 같이 저장해 줌
다대다:양방향
- 역방향도 @ManyToMany 사용
- 양쪽 중 원하는 곳에 mappedBy 로 연관관계의 주인을 지정한다. (mappedBy가 없는 곳이 연관관계의 주인)
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private Long 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 Long id;
private String name;
@ManyToMany(mappedBy="products")
private List<Member> members;
}
다대다:매핑의 한계와 극복, 연결 엔티티 사용
- 보통 연결 테이블에 주문 수량 컬럼이나 주문한 날짜 같은 컬럼이 더 필요하다. => 이럴땐 @ManyToMany 사용 불가
왜냐하면 주문 엔티티나 상품 엔티티에는 추가한 걸럼들을 매핑할 수 없기 때문이다. - 결국 연결테이블을 매핑하는 연결 엔티티를 만들어야 한다.
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private Long id;
@OneToMany(mappedBy="member")
private List<MemberProduct> memberProducts;
}
@Entity
public class Product {
@Id
@Column(name="PRODUCT_ID")
private Long 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;
}
//회원상품 식별자 클래스
public class MemberProductId implements Serializable {
private String member; // MemberProduct.member와 연결
private String product; // MemberProduct.product와 연결
@Override
public boolean equals(Object o) {...}
@Override
public int hashCode() {...}
}
- MemberProduct 엔티티를 보면기본키를 매핑하는 @Id와외래키를 매핑하는@JoinColumn을 동시에 사용해서 기본키+외래키 매핑
@IdClass를 사용해서복합 기본키매핑
※ 복합 기본키 (복합키) ※
- JPA에서 복합키를 사용하려면 별도의 식별자 클래스 생성해야함
- Serializable을 구현해야 함
- equals와 hashCode 메소드를 구현해야 함 (자바 IDE에는 대부분 자동으로 생성해줌)
- 기본 생성자가 있어야 함
- 식별자 클래스는 public 이어야 함
- @IdClass를 사용하는 방법 외에 @EmbeddedId를 사용하는 방법도 있음
※ 식별관계 ※
- 회원상품은 회원과 상품의 기본키를 받아서 자신의 기본키로 사용함
이렇게 부모 테이블의 기본키를 받아서 자신의 기본키 + 외래키로 사용하는 것을 식별관계라 한다.
다대다:새로운 기본키 사용
- 복합키를 사용하는 방법은 복잡하다. 복합키를 사용하지 않고 간단히 다대다 관계를 구성하는 방법을 알아보자.
- 추천하는 기본키 생성 전략은 데이터베이스에서 자동으로 생성해주는 대리키를 Long값으로 사용하는 것
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private Long id;
@OneToMany(mappedBy="member")
private List<Order> orders = new ArrayList<Order>();
}
@Entity
public class Product {
@Id
@Column(name="PRODUCT_ID")
private Long id;
private String name;
}
// Order로 이름 변경
@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;
}
- 식별자 클래스를 사용하지 않아서 코드가 한결 단순해짐
출처도서 : 자바 ORM 표준 JPA 프로그래밍 - 김영한 지음
'JPA' 카테고리의 다른 글
[개념] JPA 프로그래밍 - 10. 객체지향 쿼리 언어 (0) | 2019.12.27 |
---|---|
[개념] JPA 프로그래밍 - 07. 고급매핑 (0) | 2019.12.27 |
[개념] JPA 프로그래밍 - 05. 연관관계 매핑 기초 (0) | 2019.12.27 |
[개념] JPA 프로그래밍 - 04. 엔티티 매핑 (0) | 2019.12.27 |
[개념] JPA 프로그래밍 - 03. 영속성 관리 (0) | 2019.12.27 |