"인프런 - 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 작성한 글 입니다."
www.inflearn.com/course/ORM-JPA-Basic#
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 프로그
www.inflearn.com
임베디드 타입과 같은 값 타입을 컬렉션에 담아서 사용할 수 있을까?
값 타입 컬렉션은 값 타입을 하나 이상 저장할 때 사용할수 있다.
그런데 값 타입을 컬렉션에 담아서 사용하는 경우 DB 테이블로 구현할 때 문제가 발생한다.
- 단순하게 값 타입이 하나일 때는 필드 속성으로 활용하여 테이블에 넣으면 되는데, 관계형 데이터베이스는 기본적으로 컬렉션을 담을 수 있는 구조가 없다.(데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.)
* 컬렉션은 결국 일대다 개념이다
: 그러므로 해당 컬렉션에 대해 별도의 테이블을 생성해야 한다.
(컬렉션 들은 일대다 개념이기 때문에 DB 에서 한 테이블에 넣을 수 있는 방법이 없다, 그렇기 때문에 일대다 로 풀어서 별도의 테이블로 만들어 내야 한다.)
- 임베디드 타입의 경우 해당 타입에 속하는 필드 속성들을 묶어서 하나의 기본 키로 만들어야 한다.
왜? 값 타입 테이블에서 식별자 id 값을 가져와서 기본 키로 사용하게 되면, 해당 테이블은 값 타입의 테이블이 아니라 Entity 가 되어버리기 때문이다.
- 값 타입 컬렉션의 테이블을 생성하기 위해 @ElementCollection, @CollectionTable 어노테이션을 사용할 수 있다.
- Member 도메인 클래스에서 임베디드 타입의 컬렉션을 별도의 테이블로 만들어보자.
- Member.java
@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD", joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME")
private Set<String> favoriteFood = new HashSet<>();
@ElementCollection
@CollectionTable(name = "ADDRESS", joinColumns = @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();
@ElementCollection 어노테이션
- 값 타입 컬렉션임을 지정하는 어노테이션이다.
@CollectionTable 어노테이션
- 해당 컬렉션의 테이블을 생성하는 어노테이션이다.
- name 속성 : 테이블의 이름을 지정해 줄 수 있다.
- joinColumns 속성 : @JoinColumn 어노테이션을 통해 외래 키 값을 지정해 줄 수 있다.
위의 코드를 보면 favoriteFood 객체의 컬렉션 타입인 Set 컬렉션의 경우, 임베디드 타입을 Address 타입으로 가지는 List 컬렉션과 달리 컬렉션의 데이터 필드가 String 하나 밖에 없는 상태이기 때문에 @Column 어노테이션의 name 속성을 통해서 컬럼 명을 String 이 아닌 다른 이름으로 지정해 줄 수 있다.
(Address 타입의 경우 임베디드 타입 내부에 속해있는 값 타입들의 변수 명을 그대로 사용하면 된다 : city, street, zipcode)
애플리케이션을 실행해보면 아래와 같은 hibernate SQL 을 출력하는 것을 확인 할 수 있다.
- hibernate SQL
Hibernate:
create table Member (
MEMBER_ID bigint not null,
city varchar(255), // homeAddress field
street varchar(255), // homeAddress field
ZIPCODE varchar(255), // homeAddress field
USERNAME varchar(255),
endDate timestamp,
startDate timestamp,
TEAM_ID bigint,
primary key (MEMBER_ID)
)
Hibernate:
create table FAVORITE_FOOD ( //String 타입 Set 컬렉션 테이블 생성
MEMBER_ID bigint not null,
FOOD_NAME varchar(255)
)
Hibernate:
create table ADDRESS ( // 임베디드 타입인 Address 타입 List 컬렉션 테이블 생성
MEMBER_ID bigint not null,
city varchar(255),
street varchar(255),
ZIPCODE varchar(255)
)
이번엔 아래의 코드를 작성하여 값 타입 컬렉션에 데이터를 삽입 한 후 애플리케이션을 실행해보자.
- JpaMain.java
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "street", "10000"));
member.getFavoriteFood().add("치킨");
member.getFavoriteFood().add("족발");
member.getFavoriteFood().add("피자");
member.getAddressHistory().add(new Address("old1", "street", "10000"));
member.getAddressHistory().add(new Address("old2", "street", "10000"));
em.persist(member);
애플리케이션을 실행해보면 아래와 같은 hibernate SQL 을 출력하는 것을 확인할 수 있다.
- hibernate SQL
Hibernate: // homeAddress 데이터 삽입
/* insert hellojpa.Member
*/ insert
into
Member
(city, street, ZIPCODE, USERNAME, endDate, startDate, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?, ?)
Hibernate: // addressHisotory 데이터 삽입
/* insert collection // 컬렉션에 데이터를 삽입한다.
row hellojpa.Member.addressHistory */ insert
into
ADDRESS
(MEMBER_ID, city, street, ZIPCODE)
values
(?, ?, ?, ?)
Hibernate: // addressHisotory 데이터 삽입
/* insert collection
row hellojpa.Member.addressHistory */ insert
into
ADDRESS
(MEMBER_ID, city, street, ZIPCODE)
values
(?, ?, ?, ?)
Hibernate: // favoriteFood 데이터 삽입
/* insert collection
row hellojpa.Member.favoriteFood */ insert
into
FAVORITE_FOOD
(MEMBER_ID, FOOD_NAME)
values
(?, ?)
Hibernate: // favoriteFood 데이터 삽입
/* insert collection
row hellojpa.Member.favoriteFood */ insert
into
FAVORITE_FOOD
(MEMBER_ID, FOOD_NAME)
values
(?, ?)
Hibernate: // favoriteFood 데이터 삽입
/* insert collection
row hellojpa.Member.favoriteFood */ insert
into
FAVORITE_FOOD
(MEMBER_ID, FOOD_NAME)
values
(?, ?)
여기서 특이한 점은 값 타입 컬렉션을 따로 persist 하지 않고 member 만 persist 하니까 값 타입 컬렉션들이 자동으로 같이 persist 되었다.
즉, 다른 테이블인데도 불구하고 라이프 사이클이 같이 돌아갔다.(member 를 저장할 때 같이 들어간 것)
왜? 해당 Entity 에 속하는 값 타입이기 때문이다.
값 타입 컬렉션도 본인 스스로의 라이프 사이클이 없다.(모든 라이프 사이클이 member 에 소속 된다.)
즉, member 의 값을 바꾸거나 하면 같이 바뀐다는 뜻이다.
값 타입들은 별도로 update 하거나 persist 할 필요가 없다. 그냥 member 에서 값을 바꾸면 자동으로 update 된다.
일대다 연관관계 에서 영속성 전이 기능인 cascade 옵션을 ALL 로 넣고 고아 객체 옵션인 orphanRemoval 을 true 로 지정해 놓은 것과 비슷하다고 볼 수 있다.
(갑 타입 컬렉션은 영속성 전이, 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.)
여기서 find 메소드를 통해 member 객체를 조회할 경우 hibernate SQL 이 어떻게 출력될까?
- JpaMain.java
System.out.println("===============START================");
Member findMember = em.find(Member.class, member.getId());
- hibernate SQL
===============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_.USERNAME as username5_6_0_,
member0_.endDate as enddate6_6_0_,
member0_.startDate as startdat7_6_0_
from
Member member0_
where
member0_.MEMBER_ID=?
위의 hibernate SQL 을 보면 member 테이블에만 select 쿼리를 전달한다는 것을 알 수 있다.
(이때, homeAddress 와 같이 컬렉션 테이블이 아닌 일반적인 임베디드 타입은 함께 select 된다.)
그 말은 즉, 컬렉션 테이블들은 모두 지연 로딩(LAZY) 이라는 뜻이다.
이제 아래의 코드를 추가로 작성한 후 애플리케이션을 실행해보자.
- JpaMain.java
List<Address> addressesHistory = findMember.getAddressHistory();
for (Address address : addressesHistory){
System.out.println("address = " + address.getCity());
}
Set<String> favoriteFoods = findMember.getFavoriteFood();
for (String favoriteFood : favoriteFoods){
System.out.println("favoriteFood = " + favoriteFood);
}
- hibernate SQL
===============START================
Hibernate: // findMember 객체 데이터 select
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_.USERNAME as username5_6_0_,
member0_.endDate as enddate6_6_0_,
member0_.startDate as startdat7_6_0_
from
Member member0_
where
member0_.MEMBER_ID=?
Hibernate: // 컬렉션 타입을 직접 호출하여야 해당 컬렉션 테이블에 대한 select 쿼리가 전달된다.(지연 로딩 - LAZY)
// AddressHistory 컬렉션 데이터 select
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 = old1
address = old2
Hibernate: // FavoriteFood 컬렉션 데이터 select
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 = 피자
위의 hibernate SQL 을 보면, 출력문에서의 getCity() 메소드와 컬렉션 객체 변수 favoriteFoods 처럼 컬렉션 타입을 직접 사용하는 코드가 실행되면 지연 로딩(LAZY) 기능을 통해 그제서야 해당하는 컬렉션에 대해 select 쿼리가 DB 에 전달되는 것을 확인할 수 있다.
다음 글에서는 컬렉션 에서늬 값 타입 데이터를 수정해보자.
'JPA' 카테고리의 다른 글
실전 예제 6 - 값 타입 (0) | 2020.10.22 |
---|---|
값 타입 - 값 타입 컬렉션(2) (0) | 2020.10.22 |
값 타입 - 불변 객체와 값 타입 비교(2) (0) | 2020.10.22 |
값 타입 - 불변 객체와 값 타입 비교(1) (0) | 2020.10.21 |
값 타입 - 기본 값 타입, 임베디드 타입(3) (0) | 2020.10.21 |