JPA의 데이터 타입은 크게 엔티티와 값 타입으로 나뉜다.
여태까지는 엔티티를 많이 다뤄봤으니 이제 값 타입을 알아보자.
시작하기 앞서 기존 엔티티와 어떻게 다른지 둘을 비교해보고,
상세한 내용들은 목차를 따라가면서 알아가자.
엔티티 타입
@Id
)가 존재한다.값 타입
지금부터 예제 코드를 통해서 언제 이러한 값 타입을 쓰면 좋은지 알아보자.
조금 억지스러운 예제이지만 이해해주길 바란다.
엔티티 코드
// 기타 엔티티
@Entity
@Setter @Getter
public class Guitar {
@Id @GeneratedValue
private Long id;
private String name; // 기타 이름
// 기타 메이커(상표)와 관련된 정보들
private String makerName; // 메이커(상표) 이름
private LocalDate establishDate;// 건립일
private String ceoName; // CEO 이름
}
위의 엔티티 코드에서 메이커 정보
는 DB Table 상에 있는 컬럼이니 당연히 필드가 있기는 해야한다. 하지만 객체의 세계에서는 이런 관련된 정보들을 하나의 클래스로 모아 놓는 게 좋다.
이때 필요한 게 값 타입이다.
아래 예시 코드를 보자.
엔티티 코드
// 기타 엔티티
@Entity
@Setter @Getter
public class Guitar {
@Id @GeneratedValue
private Long id;
private String name; // 기타 이름
@Embedded
private Maker maker; // 값타입 사용!
// 유틸리성 메소드 추가...
}
값 타입 클래스 생성
// 악기 메이커 엔티티
// ex: Martin, Yamaha
@Embeddable // 값 타입 클래스에는 필수 작성
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
public class Maker {
private String makerName; // 메이커(상표) 이름
private LocalDate establishDate;// 건립일
private String ceoName; // CEO 이름
}
@NoArgsConstructor(access = AccessLevel.PROTECTED)
를 하는 이유와 setter
메소드를 생성하지 않는 안하는 이유는 값 타입의 안정성
파트에서 알아볼 것이다.AUTO DDL QUERY
create table guitar (
id bigint not null,
ceo_name varchar(255),
establish_date date,
maker_name varchar(255),
name varchar(255),
primary key (id)
)
값 타입은 JPA에서 사용하려면 @Embedded
, @Embeddable
애노테이션을 사용해야 한다.
이런 값타입은 엔티티 persist 시 어떻게 사용할까?
아래 테스트 코드를 보자.
테스트 코드
Guitar guitar = new Guitar();
Maker maker = new Maker("martin", // 상품명
LocalDate.of(1833, 9, 9), // 회사 건립일
"Christian Frederick Martin"); // 회사 건립자(= CEO )
guitar.setMaker(maker);
guitar.setName("GRAND J-16E");
em.persist(guitar);
테이블 확인
생성자
또는 Builder
를 사용한다.아래 코드를 보자.
@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 1. PROTECTED 접근자 사용
@AllArgsConstructor
@Getter // 2. Setter 메소드 없음
public class Maker {
private String makerName;
private LocalDate establishDate;
private String ceoName;
// 3. 편의 메소드 추가
public Maker createCopy() {
return new Maker(this.makerName, this.establishDate, this.ceoName);
}
}
1. @NoArgsConstructor(access = AccessLevel.PROTECTED)
설명
값 타입 클래스는 초기에만 초기화가 되도록 생성자를 쓴다고 하였다.
그런데 @Embeddable
클래스는 기본 생성자가 필수다.
하지만 필수라 하여도 쓸모가 없다. 그러니 외부에서 최대한 못 쓰도록 막아 놓은 것이다.
2. setter
사용 ❌
공유 상태에서 서로 값을 바꾸는 것을 막기 위해서 애초에 setter를 생성하지 않는 것이다.
3. createCopy
편의 메소드 (Optional)
다른 엔티티도 값들을 갖고 있는 값 타입 객체를 원할 수 있다.
그때 사용하기 위한 편의 메소드이다.
equal
, hashcode
메소드를 구현해주자.
@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor @Getter
@EqualsAndHashCode // 롬복은 간단하게 애노테이션 추가!!
public class Maker { ~ }
경고! 미리 말하지만 값 타입 컬렉션 보다는 엔티티를 사용하는 것을 추천한다.
이전에는 엔티티 내에서 값 타입을 사용할 때는 Collection 이 아닌 단일 타입으로 사용했다.
이번에는 Collection 타입으로 값타입을 사용해 보자.
엔티티 코드
// 악기점 엔티티
@Entity
@Getter @Setter
public class MusicStore {
@Id @GeneratedValue
@Column(name = "music_store_id")
private Long id; // 아이디
private String name; // 가게 명
@ElementCollection
@CollectionTable(name = "instrument_types",
joinColumns = @JoinColumn(name = "music_store_id"))
@Column(name = "inst_type") // Collection의 값이 하나면 @Column 사용 가능
private Set<String> instTypeList = new HashSet<>(); // 악기 종류 리스트
@ElementCollection
@CollectionTable(name = "maker_for_sale",
joinColumns = @JoinColumn(name = "music_store_id"))
private List<Maker> makerList = new ArrayList<>(); // 파는 악기의 메이커 리스트
}
생성되는 테이블의 모습
값 타입 컬렉션 하나당 하나의 테이블과 매핑된다.
그리고 이 테이블의 이름은 @CollectionTable
을 통해서 지정이 가능하다.
만들어봤으니 이제 테스트를 돌려보자.
값 컬렉션 테스트 코드 - INSERT
MusicStore musicStore = new MusicStore();
musicStore.setName("musicStore");
musicStore.getInstTypeList().add("guitar");
musicStore.getInstTypeList().add("bass");
musicStore.getInstTypeList().add("drum");
musicStore.getMakerList().add(new Maker("maker1", LocalDate.now(), "ceo1"));
musicStore.getMakerList().add(new Maker("maker2", LocalDate.now(), "ceo2"));
musicStore.getMakerList().add(new Maker("maker3", LocalDate.now(), "ceo3"));
em.persist(musicStore);
Table 확인
값 컬렉션 테스트 코드 - SELECT
MusicStore musicStore = em.find(MusicStore.class, 1L);
Set<String> instTypeList = musicStore.getInstTypeList();
for (String type : instTypeList) {
System.out.println("type = " + type);
}
// @ElementCollection 은 기본이 fetch = Fetch.LAZY 이다!
List<Maker> makerList = musicStore.getMakerList();
System.out.println("111111111111111111111");
for (Maker maker : makerList) {
System.out.println("maker = " + maker.getMakerName());
}
System.out.println("2222222222222222222222");
콘솔 출력 확인
값 컬렉션 테스트 코드 - DELETE
List<Maker> makerList = musicStore.getMakerList();
makerList.remove(1); // 3개중 1개만 삭제
참고. 값 타입 컬렉션은 이전에 배운 Cascade, 고아객체의 기능을 포함하고 있다.
콘솔 출력 확인
3개 전체를 다 지우고 남는 2개를 다시 insert 한다 ->
값 타입 컬렉션 한계
수정은 단순히 remove + add 를 번갈아서 사용하는 것이므로 PASS!
추적할 필요도 없고 값이 바뀌어도 update 칠 필요가 없을 때 값 타입 컬렉션을 쓰자.
정말 단순할 때 사용한다.