Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- transaction
- 스프링 핵심 기능
- Servlet
- jpa
- Android
- 김영한
- SpringBoot
- JDBC
- 인프런
- Thymeleaf
- Spring Boot
- 자바
- 그리디
- AOP
- Greedy
- 백준
- spring
- http
- db
- kotlin
- Exception
- pointcut
- Proxy
- 알고리즘
- springdatajpa
- JPQL
- QueryDSL
- 스프링 핵심 원리
- 스프링
- java
Archives
- Today
- Total
개발자되기 프로젝트
값 타입 본문
1. 값 타입 컬렉션이란??
- 값 타입을 컬렉션에 담아 사용하는 것.
- 관계형 DB는 기본적으로 내부에 컬렉션을 담을 방법이 없음. 값만 넣을 수 있음.
- 그래서 컬렉션에 해당하는 것을 별도 테이블로 만들어야함. 즉 일대다 형태로 구성됨.
- 단 이때 값타입의 테이블은 각 속성들을 모아서 PK로 사용해야함.
- 만약 식별자를 만들어서 PK로 쓰면 엔티티가 됨 ㅋㅋㅋㅋ
2. 값 타입 컬렉션
- 값 타입을 하나 이상 저상할 때 사용
- @ElementCollection, @CollectionTable 사용
- @ElementCollection을 통해 값 타입 컬렉션으로 지정
- @ColellectionTable을 사용해 테이블 생성
- 이 때 일종의 일대다 관계라 볼 수 있고, FAVORITE_FOOD와 ADDRESS가 "다"측이고,
따라서 "일"의 PK를 가지고 있어야 한다. --> @JoinColumn -
@ElementCollection @CollectionTable(name = "FAVORITE_FOOD", joinColumns = @JoinColumn(name = "MEMBER_ID")) //Collection table에 FK 넣어줌.FK로 잡게됨. @Column(name = "FOOD_NAME") private Set<String> favoriteFoods = new HashSet<>(); @ElementCollection @CollectionTable(name = "ADDRESS", joinColumns = @JoinColumn(name = "MEMBER_ID")) private List<Address> addressHistory = new ArrayList<>();
- 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
- 컬렉션을 저장하기 위한 별도의 테이블이 필요함.
- DB에는 컬렉션을 한 테이블에 담을 수 없다. 일대다 구조로 생성해야 한다.
3. table 생성 결과
- 실행 결과
- Member table에 값 타입 들어감.
Hibernate: create table Member ( MEMBER_ID bigint not null, city varchar(255), street varchar(255), zipcode varchar(255), endDate timestamp, startDate timestamp, USERNAME varchar(255), TEAM_ID bigint, primary key (MEMBER_ID) )
- FAVORITE_TABLE, ADDRESS TABLE 생성
Hibernate: create table FAVORITE_FOOD ( MEMBER_ID bigint not null, FOOD_NAME varchar(255) ) Hibernate: create table ADDRESS ( MEMBER_ID bigint not null, city varchar(255), street varchar(255), zipcode varchar(255) )
- Member table에 값 타입 들어감.
4. 값 타입 컬렉션 저장 예제
Member member = new Member();
member.setUserName("member1");
//address 값타입
member.setHomeAddress(new Address("homeCity", "street", "sdd2d"));
//값타입 컬렉션
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피쟈");
member.getAddressHistory().add(new Address("old1", "street", "65555565"));
member.getAddressHistory().add(new Address("old2", "street", "65555565"));
em.persist(member);
tx.commit();
쿼리를 보자!
- 처음 값 타입인 address가 Member에 insert됨
-
Hibernate: /* insert hellojpa.Member */ insert into Member (city, street, zipcode, endDate, startDate, TEAM_ID, USERNAME, MEMBER_ID) values (?, ?, ?, ?, ?, ?, ?, ?)
-
- 이후 insert collection이 실행됨.
- ADDRESS에 두 번
Hibernate: /* insert collection row hellojpa.Member.addressHistory */ insert into ADDRESS (MEMBER_ID, city, street, zipcode) values (?, ?, ?, ?) Hibernate: /* insert collection row hellojpa.Member.addressHistory */ insert into ADDRESS (MEMBER_ID, city, street, zipcode) values (?, ?, ?, ?)
- FAVORITE_FOOD에 세 번
Hibernate: /* insert collection row hellojpa.Member.favoriteFoods */ insert into FAVORITE_FOOD (MEMBER_ID, FOOD_NAME) values (?, ?) Hibernate: /* insert collection row hellojpa.Member.favoriteFoods */ insert into FAVORITE_FOOD (MEMBER_ID, FOOD_NAME) values (?, ?) Hibernate: /* insert collection row hellojpa.Member.favoriteFoods */ insert into FAVORITE_FOOD (MEMBER_ID, FOOD_NAME) values (?, ?)
- em.persist(member)하나로 쿼리들이 모두 나갔다.
- 매핑이 잘 되었당 DB 모두 반영되었다.
- ADDRESS에 두 번
5. 값 타입 컬렉션 조회 예제
- 값 타입 컬렉션도 지연 로딩 전략 사용해야함.
- 영속성 컨텍스트를 싹 비우고 member를 조회해 보자.
-
Member member = new Member(); member.setUserName("member1"); //address 값타입 member.setHomeAddress(new Address("homeCity", "street", "sdd2d")); //값타입 컬렉션 member.getFavoriteFoods().add("치킨"); member.getFavoriteFoods().add("족발"); member.getFavoriteFoods().add("피쟈"); member.getAddressHistory().add(new Address("old1", "street", "65555565")); member.getAddressHistory().add(new Address("old2", "street", "65555565")); em.persist(member); em.flush(); em.clear(); System.out.println("===============START============="); Member findMember = em.find(Member.class, member.getId()); tx.commit();
- ? Member에 대해서만 Select 쿼리가 실행되었다.
- 즉, 값 타입 컬렉션에 대해서는 지연로딩이 적용되고 있는 것이다.
- 반면에 임베디드타입은 바로 불러옴 ㅋㅋㅋ
===============START============= Hibernate: select member0_.MEMBER_ID as member_i1_6_0_, member0_.city as city2_6_0_, member0_.street as street3_6_0_, member0_.zipcode as zipcode4_6_0_, member0_.endDate as enddate5_6_0_, member0_.startDate as startdat6_6_0_, member0_.TEAM_ID as team_id8_6_0_, member0_.USERNAME as username7_6_0_ from Member member0_ where member0_.MEMBER_ID=?
- 컬렉션을 사용할 때 쿼리가 나가게됨.
Member member = new Member(); member.setUserName("member1"); //address 값타입 member.setHomeAddress(new Address("homeCity", "street", "sdd2d")); //값타입 컬렉션 member.getFavoriteFoods().add("치킨"); member.getFavoriteFoods().add("족발"); member.getFavoriteFoods().add("피쟈"); member.getAddressHistory().add(new Address("old1", "street", "65555565")); member.getAddressHistory().add(new Address("old2", "street", "65555565")); em.persist(member); em.flush(); em.clear(); System.out.println("===============START============="); Member findMember = em.find(Member.class, member.getId()); System.out.println("===============ADDRESS============="); List<Address> addressHistory = findMember.getAddressHistory(); for (Address address : addressHistory) { System.out.println("address = " + address); } System.out.println("===============FOOD============="); Set<String> favoriteFoods = findMember.getFavoriteFoods(); for (String favoriteFood : favoriteFoods) { System.out.println("favoriteFood = " + favoriteFood); } tx.commit();
-
===============ADDRESS============= Hibernate: select addresshis0_.MEMBER_ID as member_i1_0_0_, addresshis0_.city as city2_0_0_, addresshis0_.street as street3_0_0_, addresshis0_.zipcode as zipcode4_0_0_ from ADDRESS addresshis0_ where addresshis0_.MEMBER_ID=? address = hellojpa.Address@8dfd80a4 address = hellojpa.Address@8dfd8465 ===============FOOD============= Hibernate: select favoritefo0_.MEMBER_ID as member_i1_4_0_, favoritefo0_.FOOD_NAME as food_nam2_4_0_ from FAVORITE_FOOD favoritefo0_ where favoritefo0_.MEMBER_ID=? favoriteFood = 족발 favoriteFood = 피쟈 favoriteFood = 치킨
6. 값 타입(컬렉션) 수정
- 값 타입은 불변하다. IMMUTABLE
- 따라서 값 타입 수정하기 위해서는 생성자를 통해 새로 만들고 넣어줘야 한다.
Address a = new Address("new city", "new Street", "new zip code"); findMember.setHomeAddress(a);
- String은 업데이트가 안된다. 지웠다가 다시 넣어줘야 한다. String도 값 타입이다.
//치킨 -> 한식으로 바꾸고픔 //String 은 갈아 끼워야함 ㅋㅋㅋㅋ findMember.getFavoriteFoods().remove("치킨"); findMember.getFavoriteFoods().add("한식");
- 값 타입 컬렉션은 어떻게??? 똑같이 지웠다가 새로 넣어줘야 한다.
- 다만 컬렉션은 일반적으로 remove 등 대상을 찾을 때 equals()를 사용한다.
- 따라서 equals(), hash()가 override되어 있어야 정상적으로 사용할 수 있다.
- 값 타입은 동일성 비교가 아니라 동등성 비교를 해야하기 때문.
//주소 히스토리 바꾸자 //기본적으로 컬렉션은 대상을 찾을 때 equals를 사용함. //Address에 equals()를 override했기 때문에 동등성 비교를 시행함. findMember.getAddressHistory().remove(new Address("old1", "street", "65555565")); findMember.getAddressHistory().add(new Address("new1", "street", "65555565"));
- 그런데 remove를 하면 member과 관련된 Address_History 테이블이 전부 삭제되었다가 insert 된다..ㅠ
- 결과적으로는 의도한대로 입력이 되긴 했는데.. --> 6번으로 가자.
7. 값 타입 컬렉션 사용.
- 값 타입의 라이프사이클이 엔티티를 따라가는 것 처럼
- 값 탕입 컬렉션도 라이프사이클이 엔티티를 따라간다.
- 즉, 값타입 컬렉션은 CASCADE + 고아객체 제거 기능을 필수로 가져간다고 볼 수 있다.+
8. 값 타입 컬렉션의 제약사항
- 값 타입은 엔티티와 다르게 식별자 개념이 없다.
- 값은 변경하면 추적이 어렵다.
- 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고,
값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다. - 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 함
- null 입력 안되고 중복도 안됨.
- 쓰지마 ㅋㅋㅋㅋㅋㅋㅋㅋ
- 값 타입 컬렉션의 테이블은 보면 ID가 없어. 추적도 안됨.
- 이렇게 복잡하게 쓸거면 하지마 그냥 다르게할거야 ㅋㅋㅋㅋ🤣
9. 값 타입 컬렉션 대안
- 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려. 훨씬 편한데..?
- 일대다 관계를 위한 엔티티를 만들고, 여기에서! 값 타입을 활용하면 됨
- CASCADE + 고아 객체 제거를 사용해서 값타입 컬렉션 처럼 사용하면됨.
- AddressEntity
@Entity @Table(name = "ADDRESS") public class AddressEntity { @Id @GeneratedValue private Long id; private Address address; }
- Member
//일대다 단방향 매핑. @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "MEMBER_ID") private List<AddressEntity> addressHistory = new ArrayList<>();
- Test
깔끔하게 엔티티로 저장할 수 있다.
Member member = new Member(); member.setUserName("member1"); //address 값타입 member.setHomeAddress(new Address("homeCity", "street", "sdd2d")); //값타입 컬렉션 member.getFavoriteFoods().add("치킨"); member.getFavoriteFoods().add("족발"); member.getFavoriteFoods().add("피쟈"); member.getAddressHistory().add(new AddressEntity("city", "street", "zipcode")); member.getAddressHistory().add(new AddressEntity("city2", "street", "zipcode")); em.persist(member); em.flush(); em.clear(); tx.commit();
10. 정리
- 엔티티 타입의 특징
- 식별자 있음
- 생명 주기 관리
- 공유됨.(참조값)
- 값 타입의 특징
- 식별자 없음
- 생명 주기를 엔티티에 의존
- 공유하지 않는 것이 안전(새로 만들어서 값을 복사해서 사용)
- 불변 객체로 만들어.
- 값 타입은 정~말 값 타입이라 판단될 경우에만 사용하자.
- 엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨
- 식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면!! 값 타입이 아닌 엔티티임.
11. GitHub : 210813 값 타입 컬렉션
'인프런 > [인프런] 자바ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
객체지향 쿼리 언어 (0) | 2021.08.14 |
---|---|
[예제] 값 타입 매핑 (0) | 2021.08.14 |
값 타입 비교 (0) | 2021.08.13 |
값 타입과 불변 객체 (0) | 2021.08.13 |
임베디드 타입 (0) | 2021.08.12 |
Comments