[자바 ORM 표쥰 JPA 프로그래밍] 12일차 - 엔티티 매핑
DB 테이블은 PK와 FK를 가지고 테이블간 관계를 맺어주는데
엔티티는 기본적으로 단방향 매핑 밖에 안되어 JPA가 별도의 주인을 정하여 양방향 매핑을 가능토록 해준다.
테이블 관계에서 4가지의 유형이 있다
- 1 : 1 - 서로 1대1로 매핑하는 관계로 테이블 정규화시 보통 이러한 관계로 나타내곤한다.
- 1 : N - 1 대 N 으로 하나의 ROW가 N개의 ROW와 매핑이 가능한 상태이며 가장 많이 나오는 관계이다.
- N : 1 - 1 대 N 과 동일하다.
- N : N - N 대 N 관계로 실제 사용하기에는 복잡하여 보통 정규화를통해 1:N 이나 N:1로 추가한다.
1. 다대일 관계
일반적으로 테이블에서는 관계를 쉽게 표현할 수 있기 때문에 단방향, 양방향이라는 단어를 쓰진않는다.
하지만 엔티티의 경우 객체끼리의 단방향은 할수는 있어도 서로를 참조하는 양방향은 못한다.
public class Member {
// Member -> Team 를 단방향 참조
private Team team;
...
}
public class Team {
// Team -> Member 로 단방향 참조
private Member member;
...
}
그렇기 때문에 JPA는 외래키를 갖고있는 엔티티를 주인으로 선정하여
주인 엔티티를 기준점으로 서로를 참조할 수 있게끔 기능을 지원해준다.
@Entity
public class Member {
@ManyToOne // Member가 주인이 되어 Team를 참조
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
@Entity
public class Team {
@OneToMany(mappedBy = "team") // Member 가 주인이며 참조역할만 해준다.
private List<Member> members = new ArrayList<Member>();
...
}
Member 가 N 이 되며 Team 1 이 되어 N:1 관계를 엔티티로 표현하였다.
2. 일대일 관계
1:1 관계에서는 주 테이블과 대상 테이블 중 하나를 택하여 외래키를 주어지게 되는데
예를 들어 MEMBER 와 LOCKER 는 1:1 관계로 맺어져야한다면
MEMBER는 대상 테이블이 되는 것이고 LOCKER 가 주 테이블이 된다.
주 테이블에 외래키를 갖게되는 것은 객체 참조를 쉽게 할수있어 객체지향개발자들이 선호하고
대상 테이블에 외래키를 갖는 것은 DB개발자들이 선호한다.
@Entity // 대상 테이블
public class Member {
@OneToOne
@JoinColumn(name = "LOCKER_ID") // 주인
private Locker locker;
...
}
@Entity // 주 테이블
public class Locker {
@OneToOne(mappedBy = "locker") // 주인이 아니여서 mappedBy를 추가
private Member member;
...
}
위는 대상 테이블에 외래키를 설정한 코드
다대일과 동일하게 작성하면 된다.
사실상 주인이 아닌 쪽에서 List 가 객체로 바뀐셈
3. 다대다 관계
일반적인 정규화된 테이블끼리는 다대다가 나올수가 없다.
관리하기도 복잡하고 실제 사용하기도 불편하기 때문이다.
JPA는 이러한 다대다 관계 테이블을 쉽게 사용하도록 제공해주는데.
N:N 용 테이블을 자동으로 생성하여 엔티티끼리는 1:N N:1 로 변경해준다.
@Entity // 대상 테이블
public class User {
@Id
@Column(name = "USER_ID")
@GeneratedValue
private long id;
@Column(name = "USER_NM")
private String name;
@ManyToMany // 주인
@JoinTable(name = "USER_ITEM",
joinColumns = @JoinColumn(name = "USER_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
private List<Item> items = new ArrayList<Item>();
}
@Entity // 주 테이블
public class Item {
@Id
@Column(name = "ITEM_ID")
@GeneratedValue
private long id;
@Column(name = "ITEM_NM")
private String name;
@ManyToMany(mappedBy = "items") // 주인이 아니여서 mappedBy 를 추가
private List<User> users = new ArrayList<User>();
}
다른 엔티티 관계 매핑과 비슷해보일수는 있는데
한가지 다른점은 주인 엔티티쪽의 @JoinTable 이라는 어노테이션을 사용했다는것이다.
이는 다대다 와 같이 번거로운 관계를 JPA가 1:N - N:1 관계로 풀어사용하려다 보니 중간에 매핑용 엔티티를 추가해야하는데
별도의 엔티티 추가조작없이 단순하게 어노테이션 속성만으로 테이블명, 매핑 컬럼을 작성하여 매핑용 테이블을 생성해준다.
4. 다대다 관계 2
실제 설계시 다대다 관계와 같이 설계될리는 없기 때문에 현실적으로는 설계에서도 중간의 매핑용 테이블을 추가하여 정규화를 진행해준다.
@Entity
public class User {
@Id
@Column(name = "USER_ID")
@GeneratedValue
private long id;
@Column(name = "USER_NM")
private String name;
@OneToMany(mappedBy = "user")
private List<UserItem> userItems = new ArrayList<UserItem>();
}
@Entity // 주 테이블
public class Item {
@Id
@Column(name = "ITEM_ID")
@GeneratedValue
private long id;
@Column(name = "ITEM_NM")
private String name;
@OneToMany(mappedBy = "item")
private List<UserItem> userItems = new ArrayList<UserItem>();
}
@Entity
@IdClass(UserItemId.class) // PK가 2개면 IdClass를 별도로 추가하여 관리
public class UserItem {
@Id
@ManyToOne
@JoinColumn(name = "USER_ID")
private User user;
@Id
@ManyToOne
@JoinColumn(name = "ITEM_ID")
private Item item;
}
// 식별용 클래스, 복합키 일때 사용된다.
public class UserItemId implements Serializable {
private long user;
private long item;
}
서로 다대다 관계인 엔티티를 중간에 정규식을 통해 엔티티를 추가하였다.
단 이는 식별관계가 되어 별도의 식별용 클래스를 생성해야한다.
- 식별관계: 식별관계란 테이블간의 FK가 PK역할을 동시에 수행하는 관계를 말함(PK -> PK +FK)
- 비식별관계: PK와 FK가 별도로 사용됨 (PK -> FK, PK)
식별용 클래스는 @Entity 어노테이션이 필요없으며 필수적으로 Serializable 를 상속받아야하며 equals나 hashcode를 필수로 구현해야하는 등 조건이 여러가지가 있다. (자세한건 나중에..)
5. 다대다 관계 3
다대다 관계2에서 처럼 설계 하되 비식별 관계로 많이들 사용한다.
비식별관계는 FK가 별도의 컬럼으로 사용되는 일반적인 관계를 말한다(식별은 PK와 FK를 같이 사용함)
이는 식별용 클래스가 필요없기 때문에 구현이 단순하다.
@Entity
public class UserItem {
@Id
@Column(name = "USER_ITEM_ID")
@GeneratedValue
private long id;
@ManyToOne // N:1
@JoinColumn(name = "USER_ID")
private User user;
@ManyToOne // N:1
@JoinColumn(name = "ITEM_ID")
private Item item;
}
다대다 관계2 번에서 UserItem 엔티티에 @IdClass 를 제거 후 PK를 하나만 생성 하여 FK용 외래키 필드를 추가하면된다.
public static void login(EntityManager em){
final User user = em.find(User.class, 1l);
for (UserItem ui : user.getUserItems()) {
System.out.println("ui = " + ui.getItem().getName());
}
}
USER PK 1만 조회하여 매핑된 모든 ITEM를 조회하는 로직