연관 관계가 있는 두 엔티티가 일대일, 일대다, 다대다 관계인지 파악해야 한다.
보통 다대일, 일대다를 가장 많이 사용하고, 다대다 관계는 거의 사용하지 않는다.
테이블은 외래 키를 사용해서 양방향 참조가 가능하다.
하지만, 객체는 참조를 통해서만 다른 객체를 조회할 수 있다.
따라서, 객체의 참조는 방향성을 갖게된다.
양방향 관계의 경우 연관 관계의 주인을 설정해야 한다.
JPA는 연관 관계의 두 객체 중 하나를 정해 외래 키를 관리한다.
즉, 외래 키의 주인이 아닌 객체는 조회만 가능하다.
외래 키를 가진 테이블과 매핑한 엔티티가 외래 키를 관리하는게 효율적이기 때문에 연관 관계의 주인으로 설정한다.
주인이 아닌 엔티티 객체는 mappedBy
속성을 사용해 주인 필드의 이름을 value
로 입력해준다.
DB의 일대다 관계에서 외래 키는 항상 "다" 쪽에 있다.
따라서, 객체의 양방향 관계에서 연관 관계의 주인은 항상 "다" 쪽이다.
양방향 연관 관계에서는 객체 간 항상 서로를 참조해야 한다. 실수하지 않기 위해서는 편의메소드를 작성해주는 것이 좋다.
이 때, 양 쪽에 다 작성해주면 무한 루프에 빠지기 때문에 주의해야한다!
@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;
...
}
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private Strimg 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;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
...
}
일대다 관계는 컬렉션을 활용한다.
DB 1:N 관계에서 외래 키는 항상 N쪽에 위치하기 때문에, 일대다 단방향 관계에선 매핑한 객체가 필드에서 관리하는 외래 키가 반대편 테이블에 존재한다.
연관 관계 처리를 위해서 INSERT SQL
이외에 추가적으로 UPDATE SQL
을 추가적으로 실행해야 한다.
Member member1 = new Member("1");
Member member2 = new Member("2");
Team team1 = new Team("1");
team1.getMembers().add(member1);
team1.getMembers().add(member2);
em.persist(member1); // INSERT
em.persist(member2); // INSERT
em.persist(team1); // INSERT, UPDATE member1.외래키, UPDATE member2.외래키
tx.commit();
반대편 테이블에서 외래 키를 관리하기 때문에 member1
, member2
가 저장될 때는 Team
을 모른다. 따라서 외래 키 값에 null
이 들어가게된다.
Team1
이 저장될 때, member
객체들의 외래 키 값이 UPDATE
된다.
따라서 일대다 단방향 매핑보다는 외래 키가 자신 테이블에 있는 다대일 양방향 매핑을 활용하는 것을 권장한다.
@OneToOne
어노테이션을 외래 키로 매핑할 필드에 추가한다.mappedBy
속성을 작성한다.RDBMS는 테이블 2 개로 다대다 관계를 표현할 수 없다.
그래서 보통 일대다 + 다대일 관계로 풀어내는 연결 테이블을 사용한다.
객체는 테이블과는 다르게 연결 테이블 객체를 만들지 않고도 2개의 객체가 컬렉션을 활용해 다대다 관계를 표현할 수 있다.
@ManyToMany
@ManyToMany
어노테이션으로 엔티티 2개로 다대다 관계를 표현할 수 있다.@JoinTable
어노테이션은 연결 테이블을 지정해준다.하지만 @ManyToMany
를 이용한 매핑은 실무에서는 쓰이기엔 한계가 명확하다.
연결 테이블에 단순히 외래 키 2개의 정보 외에 다른 정보들도 포함되기 때문이다.
결국, 연결 테이블을 매핑하는 연결 엔티티를 만들고 추가적인 정보들을 포함시켜야 한다.
정리하면, 객체 관계는 연결 테이블 없이 다대다 관계를 풀어낼 수 있다.
실무적인 문제를 고려한다면 객체 관계에서도 테이블처럼 다대다 관계를 일대다, 다대일 관계로 풀어내야한다.
부모 테이블의 기본 키를 받아서 자신의 기본 키 + 외래 키로 사용하는 것을
DB에서는 식별 관계 라고 한다.
각 엔티티의 기본 키를 받아서 기본 키 + 외래 키로 사용한다.
@IdClass
로 식별자 클래스를 만들고, 지정해야 한다.@EmbededId
를 사용하는 방법도 있다.Serializable
을 구현해야 한다.equals
와 hashcode
메소드를 구현해야 한다.public
이어야 한다.MemberProduct
클래스@Entity
@IdClass(MemberProduct.class)
public class MemberProduct {
@Id
@ManyToOne
@JoinColumn (name = "MEMBER_ID")
private Member member;
@Id
@ManyToOne
@JoinColumn (name = "PRODUCT_ID")
private Product product;
private int orderAmount;
}
MemberProductId
클래스public class MemberProductId implements Serializable {
private String member;
private String product;
@Override
public boolean equals (Object O) { ... }
@Override
public int hashcode (Object O) { ... }
}
받아온 식별자는 외래 키로만 사용하고 새로운 식별자를 추가한다.
복합 키를 사용하는 방식은 항상 식별자 클래스를 생성해야한다.
하지만, 새로운 기본 키를 사용한다면 훨씬 간편하게 매핑을 할 수가 있다.
Member
클래스@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberproducts;
...
}
Product
클래스@Entity
public class Product {
@Id
@Column(name = "PRODUCT_ID")
private String 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;
}