연관관계 매핑시 고려해야할 사항
1. 다중성 (다대일, 일대다, 일대일, 다대다) - 이건 참고로 RDBMS 를 위함임
2. 단방향 양방향
3. 연관주.
1) 다대일 다대다대
(외래키가 가는 곳이 다, 연관관계의 주인이 다, DB를 다스리는 쪽이 다, 조회만 하는쪽이 일)
(지난 시간에 한 Member : Team 연관관계로 생각)
(내가 DB에 넣을 칼럼과 동기화 시켜주면 됨. @JoinColumn(name = "team_id"))
2) 일대다 일대일대
(이건 연관주를 (일) 쪽에 두겠다는 것)
(위에 모델을 뒤집은 것)
(Team List<Members> 에서 DB를 다스림)
(하지만 DB에서는 Member 쪽에 (다) 쪽에 외래키가 들어갈 수밖에 없음. 왜냐면 Team 쪽엔 그러면 ㅅㅂ member_id 계속 넣어줘야 하잖아. 한 칼럼이 ㅈㄴ 커짐)
(정리 - 걍 쓰지 마 쫌)
3) 일대일
(이건 어떤 테이블에 외래키 넣든지 큰 관계는 없음 >> (로직상) 으로 외래키를 두는게 좋음)
(외래 키에 DB 유니크 제약조건이 추가가 되는 것)
Member (주 테이블) 와 Locker (대상 테이블)
단방향 -> 연관주 설정한다
양방향 -> 만들고 싶으면 mappedBy 를 선언하고 반대객체를 그냥 또 넣어줌.
(다대일 연관관계와 큰 차이 없음)
Locker 를 연관관계의 주인으로 잡으려면 테이블을 Locker 쪽에 걸면 됨. (반대로 이 상황에서 테이블을 Member쪽에 거는건 지원안함)
>> 다대일이랑 걍 똑같다고 보면 됨.
(참고로 DBA와 살짝 충돌이 있을 수도 있음)
(로직 상은 NULLABLE 여부로 판단하는게 좋음)
> 멤버는 라커를 가지고 있어야 해? 꼭 그런건 아니야? 그러면 Member에 랔커가 있는게 더 좋음. 혹은 더 자주 Select 할 애.
> DB 쿼리 하나로 Locker 도 같이 가지고 있을 수 있기 때문. 하지만 락커에 있으면.... 굳이 멤버를 또 한번 select 해야함. 뭐 둘다 조회 칼럼으로 두면 문제가 아니긴 하다만.
> 근데 DBA는 NULL 있는걸 싫어하기 때문에 Locker 쪽에 있는걸 좋아하기도 함.
> 근데 대상테이블이 반대쪽에 있으면 양방향으로 해야함.
나같은 경우 MemberInfo 와 1:1 관계이며, 1:1 관계에 있어서 Member가 당연히 연관관계의 주인. (MEMBER에서 Info 를 호출하는게 맞기 때문)
그렇다면
Member
MemberInfo memberInfo
MemberInfo
Member member
??
=============================== 프록시 객체 관련해서 생각해볼 관점도 있음======================
4) 다대다 연관관계
> 다대다 (실무에서 쓰면 안됨~)
> Linker 를 꼭 추가해서 일대다 관계들로 풀어내야 함. (FK 두개 가지는 객체)
> 중요한 점 : DB상 안되므로 링커를 추가해서 연관관계를 풀어내야함. 링커를 추가했어도, 각 객체에서는 List<Product>, List<Member> 를 가지고 있는 것은 객체상 가능하기 떄문에, 그 기능을 JPA에서 지원하기도 한다.
@ManyToMany
@JoinTable(name = "member_product_linker")
private List<PracticeProduct> products = new ArrayList<>();
이런식으로 추가해주면 Linker 를 거치지 않고도 조회가 가능하되, mplinker 테이블 역시 자동으로 생성된다.
하지만... LINKER 하나를 통해서 둘 사이에 그 어떤 DATA들도 엄청 들어가게 된다. 따라서 위처럼 중간 테이블 없이 저렇게 사용하는건 말이 안됨. 꼭 Entity 하나를 더 선언하여서 충분한 데이터를 활용하고, 일대다 관계들을 두개로 풀어내야 한다.
@Entity
public class MemberProductLinker {
@Id
@GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private PracticeMember member;
@ManyToOne
@JoinColumn(name = "product_id")
private PracticeProduct product;
private int count;
private int isPassedDue;
}
이런식으로 관계를 풀어내서 관계에 대한 추가적인 데이터를 활용할 수 있어야 하고, 비즈니스상 반드시 그래야함. 또한, PK도 따로 가져가는걸 추천함. PK에 무슨 "의미"가 부여되지 않는게 좋음. 즉, PK 자체적으로 1개, FK 2개 다 따로따로가 좋음. 특정 FK를 자체 PK로 활용한다? 이건 문제가 될 수도 있음.
==================================== 상속 관계 매핑===============================
DB에서도 상속관계가 정의될 수 있음. 객체 상 extends를 구현해주고 InheritanceType 을 지정해주면 됨
다음과 같은 상속 관계가 있다고 해보자.
@Entity
public class ExampleItem {
@Id
@GeneratedValue
private Long id;
private String name;
private int price;
}
@Entity
public class ItemAlbum extends ExampleItem{
private String artist;
}
@Entity
public class ItemBook extends ExampleItem {
private String author;
private String isbn;
}
@Entity
public class ItemMovie extends ExampleItem {
private String director;
private String actor;
}
가능한 테이블 전략 (@Inheritance., strategy =)
1) 조인 전략 (매우 정교)
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class ExampleItem {
...
}
정말 상속관계 그래프처럼 매핑이 됨.
(H2 DB 사진 보면 좋을듯)
각각 부모 Item 은 id를 가져간채로, 칼럼들은 따로따로 들어가 있는다. (Member, MemberInfo 관계에 적용해도 좋을 듯)
(뭐가 좋을지 생각을 한번 해봐도 좋을듯!) (DTYPE 주입 없이, 그냥 다 똑같이 부모 객체와 ID 공유한채로 들어감)
(딱 MemberInfo 객체를 넣으면, Member 객체까지 다 Set 해서 넣을 수 있고, 그러면 칼럼별로 알아서 나뉘어진채로 들어가게 됨)
@Test
@DisplayName("상속관계매핑 : InheritanceType.Joined")
void test1() {
tx.begin();
try {
ItemMovie movie = new ItemMovie();
movie.setDirector("aaa");
movie.setActor("bbbb");
movie.setName("바함사");
movie.setPrice(1232);
em.persist(movie); // 여기까지 해보고 실행시 확인이 위에 사진
// 영컨 초기화
em.flush();
em.clear();
ItemMovie itemMovie = em.find(ItemMovie.class, movie.getId());
System.out.println("itemMovie = " + itemMovie);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
itemMovie를 조회하기 위해 ItemMovie 를 조회할 경우, movie.getId()를 하여도 Item을 거쳐서 가져오게 된다. 즉, item_id 로 인식을 하고 같은 id로 inner join 을 해서 가져오게 된다.
Hibernate:
select
itemmovie0_.id as id1_4_0_,
itemmovie0_1_.name as name2_4_0_,
itemmovie0_1_.price as price3_4_0_,
itemmovie0_.actor as actor1_3_0_,
itemmovie0_.director as director2_3_0_
from
item_movie itemmovie0_
inner join
items itemmovie0_1_
on itemmovie0_.id=itemmovie0_1_.id
where
itemmovie0_.id=?
* 참고)
정말 Inheritance Data Type 이 다양하다면, ITem 에 적재되는 칼럼들이 각각 어디에 속하는 칼럼들인지 구분해줄 필요 또한 있다. 따라서 Entity 에 다음과 같이 명시를 해준다면 이에 맞게 종류를 넣어주는 DTYPE 칼럼이 자동으로 들어가게 된다.
상속받는 곳에서 @DiscriminatorValue 를 지정해주면 D-TYPE 에 들어가게 되는 값을 바꿔줄 수 있다. (DTYPE 은 엔간하면 있는게 좋음~ 쿼리 짜기도 좋음)
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class ExampleItem {
...
}
2) 단일 테이블 전략 (SINGLE_TABLE / Default 로 지정된 전략)
> 테이블 하나에 다 때려박고, DTYPE 으로 구분하는 것 (DTYPE 필수)
[성능 테스트 같은 것들도 잘 해봐야 한다] 그 결과를 통해서 어떻게 하면 더 좋은 성능이 나올 수 있을지 잘 생각해야 함.
3) 구현 클래스마다 테이블 전략
JOIN 전략이랑 비슷함. Item 을 없애버리고, Name, PRrice 로 선언된 칼럼들을 각각의 아이템들에게 때려 박아버림. 사실 객체상 연결되어 있다는 것만 빼면 그냥 다 따로따로 인거랑 큰 차이 없는 듯!
이거 좋아보이긴 하는데 언제 망하냐면, 객체지향이니까
em.find(Item.class, ~~.getId()) 했을 때, 찾을 때, ...... ㅋㅋㅋㅋㅋㅋㅋㅋ 다뒤짐.
연관된 모든 테이블 다~~~ 뒤져서 찾음. 굉장히 비효율적으로 동작.
>>>>>>>> 성능상 최악임. 절대 쓰지 말자~! 절대절대~
** 정리
Join
장: 정규화 되어 있음, 실제 Model 과 가장 가까움. 외래키를 PK로 사용할 수 있음.
단: 성능이 Single 보다 안나올 수도 있음.
Single Table
장: 조회 성능이 빠름. SQL이 단순함 (join 필요 없음)
단: 다 Null 을 허용해줘야 함. Album, Book 같은 값들을 nullable 이여야 movie 값이 들어갈 수 있음. 테이블이 지나치게 커질 수 있으므로, 조회 성능이 오히려 느려질 수도 있음.
기본은 조인 전략을 가져가되
솔직히 확장될 일도 없어보이고, 좀 단순하고 명확한 쪽이다 싶으면 Single Table도 추천!
================================================
Mapped Super Class
귀찮을 때 좋긴 좋은데, 나는 잘 모르겠음. 아닌가.
공통 매핑 정보가 필요할 때 사용. (상속, 데이터 매핑과 별 상관 없음!)
그냥 객체 입장에서 공통 매핑정보가 필요할 때 !!
>> 클래스 만들때마다 하기 좀 귀찮을 때. \
>> createdBy, createdDate, updatedBy, updatedDAte 이런거 활용할 때 좋긴 함. (이런 칼럼은 솔직히 무조건 있어야 함)
>> 상속관계 아님!! 그리고 엔티티가 아니라 테이블화되지 않는다!!! (매핑 정보만 엮어줌)
>> em.find(BaseEntity.class) 이런거 안됨. ID 도 없음.
>> 추상 클래스로 생성하는 것을 권장. 이것만 가지고 뭘 할 수 있는게 없기 때문 ( 누가 실수로 사용하는 것을 방지)
실무에서 BaseEntity 만들면 편함.
===================================================
'Spring > JPA' 카테고리의 다른 글
| [JPA] - 값 타입 (0) | 2022.12.29 |
|---|---|
| [JPA] 프록시와 연관관계 - 2 (0) | 2022.12.28 |
| [JPA] 프록시와 연관관계 정리 (0) | 2022.12.26 |
| [JPA] - 엔티티 매핑 (1) | 2022.12.21 |
| [JPA] JPA 개요와 영속성 컨텍스트 (0) | 2022.12.18 |