테이블은 외래 키 하나로 양쪽을 조인할 수 있다.
-> 방향이란 개념이 x
객체는 참조용 필드가 있는 쪽으로 조인이 가능하다.
-> 한쪽만 참조하면 단방향
-> 사실 양방향은 없는데 서로 참조하면 양방향이라고 한다.
• 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음
• 객체 양방향 관계는 A->B, B->A 처럼 참조가 2군데
-> 테이블은 하나임에 불구하고
• 객체 양방향 관계는 참조가 2군데 있음. 이 참조 2군데 중 '테이블의 외래 키(FK)'를 관리할 곳을 지정해야함
• 연관관계의 주인: 외래 키를 관리하는 참조
• 주인의 반대편: 외래 키에 영향을 주지 않음, 단순 조회만 가능
@JoinColumn
은 있으나 반대편에 연관관계 주인이 없다
• 외래 키가 있는 쪽이 연관관계의 주인
• 양쪽을 서로 참조하도록 개발
• 다대일 단방향에서 상대 클래스에 @방향(mappedby="")로 연관관계가 추가되었다고 생각하면 쉬움
• Team은 Member를 아는데 Member는 Team을 알고 싶지 않은 상황이다.
• DB입장에선 무조건 다(Member)쪽에 외래키가 들어가게 된다.
-> DB설계상 절대적이다.
• 그러면 Team의 List 값을 바꼈을 때 다른 테이블인 Member 테이블에 업데이트를 해줘야 한다.
@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<>();
....
}
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
...
}
Member member = new Member();
member.setUsername("member1");
em.persist(member);
Team team = new Team();
team.setName("teamA");
team.getMembers().add(member); // update 쿼리의 발생
em.persist(team);
UPDATE 쿼리가 추가적으로 발생한다..(이전 다대일 때랑 달리)
- 이게 문제가 된다면 실무에서 많은 테이블이 엮여서 돌아갈 때 이런 식으로 추가 쿼리가 발생된다면,,,, 문제가 발생할 것
=> 그러니 차라리 다대일 단뱡향을 쓰고 필요할 때만 '양방향'을 추가하자
• 일대다 단방향은 일대다(1:N)에서 일(1)이 연관관계의 주인
-> Q. FK 있는 쪽이 연관관계 주인 아님?
상황: 일대다는 객체 기준에서는 다에 해당하는 멤버는 일에 해당하는 팀을 모르고 있습니다. 그러나 일에 해당 하는 팀은 자신에게 속한 모든 멤버들(List members)을 알고 있고요.
- 문제
다에 해당하는 Member 테이블에는 외래키가 존재하는 반면에, Member 객체에는 Team에 대한 정보가 존재하지 않은 것입니다.
=> (결과): 어쩔 수 없이 '1'에 해당하는 Team(멤버의 정보를 들고 있는)이 외래키를 관리하는 연관관계의 주인이 되는 것입니다.- 참조 인프런
• 테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있음
• 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조
• @JoinColumn을 꼭 사용해야 함.
-> 그렇지 않으면 조인 테이블 방식을 사용함(중간에 테이블을 하나 추가함)
• 엔티티가 관리하는 외래 키가 다른 테이블에 있음
• 연관관계 관리를 위해 추가로 UPDATE SQL 실행
• 이런 매핑은 공식적으로 존재X (좀 억지스럽긴 하지만..)
• Member에서도 Team이 보고 싶어(야매로 가능함)
-> 역방향이 ok 되는 상황
• @JoinColumn(insertable=false, updatable=false)
-> 읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법
• 결론 : 다대일 양방향을 사용하자
public class Member{
@ManyToOne
@JoinColumn(name = "team_id", insertable = false, updatable = false)
private Team team;
}
...
}
//ㅆ=Team 클래스
public class Team {
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
}
근데 왜 Member 클래스에 @JoinColumn(name = "")이 "MEMBER_ID"가 아닌 "TEAM_ID"인가
- 이 말씀이 이해에 도움을 좀 주었다.
여기서는 Member를 주테이블로 생각함.
public class Member {
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
...
}
public class Locker {
@OneToOne(mappedBy = "locker")
private Member member;
...
}
mappedBy=""
를 써준다.(장점): 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
(단점): 값이 없으면 외래 키에 null 허용
(장점): 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
(단점): 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨
-> 여기서 말하는 프록시는 뒤에서 보기로 하자
Member에 locker를 조회했다고 하면, JPA 입장에서는 locker가 값이 있는지 없는지는 Locker까지 뒤져서 봐야 값이 있는지 알 수 있다.
=> 값이 있어야만 locker가 있다는 것을 알 수 있는거임.
=> 지연로딩을 하는게 아무 의미가 없음. 그저 쿼리가 하나 나가는거임
(이해가 어려운 부분이였다 이 부분은 다시 한 번 더 보자...)
public class Member {
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
...
}
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
//getter,setter...
}
@ManyToMany
사용@JoinTable
로 연결 테이블 지정- 다대다 매핑: 단방향, 양방향 가능
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToMany(mappedBy = "products")
private List<Member> members = new ArrayList<>();
}
편리한 것 같지만 실무에서 사용하지 않는다.
@OneToMany
, @ManyToOne
중간 엔티티가 PK,FK를 그대로 가지는 것 보다 오히려 PK를 따로 만들어주는 것이 더 낫다. => 유연성 때문에
PK도 더 유연하게 쓸 수 있고, 필요하면 그 때 DB에 제약조건을 쓰면 되는 것이고, JPA에서 두 개의 PK를 묶어서 만들면 새로운 composeId를 만들어야 하는 귀찮음을 감수해야 함..
또한 DB 설계에서도 장단점이 있다.
그러나 강사님은 모든 엔티티에 GeneratedValue로 PK를 만드는 식으로 개발한다.
=> id 라는게 어디에 종속되는 식으로 걸리게 되면, 시스템이 유연하게 유지보수 하는 것이 어렵다. 곧 비즈니스적으로 의미가 없는 값들이 들어갈 수도 있고 중간 수정이 되어서 PK가 추가가 되어야 한다는 식으로 PK,FK로 들어가는 것보다 훨씬 낫다.
• 주문과 배송은 1:1(@OneToOne)
• 상품과 카테고리는 N:M(@ManyToMany)
@Entity
public class Item {
@Id
@GeneratedValue
@Column(name="ITEM_ID")
private Long id;
private String name;
private int price;
private int stockQuantity;
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<>();
...
}
@Entity
public class Category {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne //자식 입장에서
@JoinColumn(name = "PARENT_ID")
private Category parent; //상위
@OneToMany(mappedBy = "parent")//parent와 양방향관계
private List<Category> child = new ArrayList<>();
@ManyToMany
@JoinTable(name = "CATEGORY_ITEM",
joinColumns = @JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID")
)
private List<Item> items = new ArrayList<>();
}
@Entity
@Table(name = "ORDERS")
public class Order {
@Id
@GeneratedValue
@Column(name = "ORDER_ID")
private Long id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID") //연관관계 주
private Member member;
//Delivery와 일대일
@OneToOne
@JoinColumn(name = "DELIVERY_ID")
private Delivery delivery;
@OneToMany(mappedBy = "order")
private List<OrderItem> orderItems = new ArrayList<OrderItem>();
...
}
@Entity
public class Delivery {
@Id @GeneratedValue
private Long id;
private String city;
private String street;
private String zipcode;
private DeliveryStatus status;
}
• 테이블의 N:M 관계는 중간 테이블을 이용해서 1:N, N:1
• 실전에서는 중간 테이블이 단순하지 않다.
• @ManyToMany는 제약: 필드 추가X, 엔티티 테이블 불일치
• 실전에서는 @ManyToMany 사용X
• 외래 키를 매핑할 때 사용
• 다대일 관계 매핑
• 다대일 관계 매핑