기본값 타입
값 타입: 식별자가 없고, 숫자 또는 문자 속성만 있다.
int, double, Integer, String... 같은 자료형을 값 타입이라고 한다.
임베디드 타입(복합 값 타입)
기존에 있는 값 타입(int, String)이 아니라 새로운 값 타입을 개발자가 직접 정의해서 사용할 수 있다.
이렇게 새롭게 만든 값 타입을 엔티티 클래스 내부에서 사용할 수 있는데 이를 임베디드 타입, 복합 값 타입이라고 한다.
임베디드 타입을 사용하면 엔티티 클래스를 깔끔하게 만들 수 있다.
Member 클래스 내부에 (id, name, startDate, endDate, city, street, zipcode)를 모두 쓰지 않고 (id, name, workPeriod, homeAddress) 만 썼다. 그리고 workPeriod, homeAddress는 임베디드 타입을 사용했다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
@Embedded
Period workPeriod;
@Embedded
Address homeAddress;
}
@Embeddable
public class Period {
@Temporal(TemporalType.DATE)
java.util.Date startDate;
@Temporal(TemporalType.DATE)
java.util.Date endDate;
...
public boolean isWork(Date date){
...
}
}
@Embeddable
public class Address {
@Column(name = "city")
private String city;
private String street;
private String zipcode;
}
Member class 는 꼭 필요한 정보들을 가지고 있게 되어 코드의 응집력이 높아지고, 코드가 명확해진다.
임베디드 타입은 기본 생성자를 필수로 생성해야 한다.
MEMBER 테이블에는 (id, name, startDate, endDate, city, street, zipcode) 컬럼들이 모두 들어있다.
테이블 수는 1개인데, 클래스 수는 3개가 된다.
이렇게 매핑 테이블 수 < 클래스 수 구조를 가지는 걸 잘 설계한 데이터베이스 구조라고 한다.
@AttributeOverride 를 통해 임베디드 타입에 정의한 매핑정보를 재정의할 수 있다.
Address 엔티티의 city 변수를 employee_city로 매핑할 수 있다.
데이터베이스 테이블에서도 employee_city 컬럼이 만들어진다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
@Embedded
Period workPeriod;
@Embedded
@AttributeOverride(name = "city", column = @Column(name = "employee_city"))
Address homeAddress;
}
값 타입과 불변 객체
값 타입은 여러 엔티티에서 공유가 가능하다
A엔티티에서 a 값타입을 사용중이라고 할 때,
a값타입의 값을 b로 바꾼 후 B 엔티티에 넣으면 A 엔티티에서도 값타입의 값이 b로 바뀐다.
이런 문제를 막기 위해 값을 clone()을 통해 복사해서 사용하고 수정을 막아야 한다.
생성자로만 값을 설정하고, 수정자가 없는 불변객체를 사용한다면
값 타입의 비교
'=='으로는 주소값을 비교할 수 있고, 'equals()'로는 인스턴스의 내부의 값을 비교할 수 있다.
따라서 값 타입을 비교할 떄는 'equals()' 를 사용해야 한다.
값 타입 컬렉션
값 타입 컬렉션은 엔티티의 속성으로 Set, List와 같은 컬렉션을 사용할 때 이용할 수 있다.
임베디드 타입과 달리 실제 테이블이 만들어진다.
주인 엔티티에 종속되기 때문에 주인 엔티티가 저장되거나 삭제될 때 값 타입 컬렉션도 함께 저장, 삭제된다.
고아 객체 + CASCADE를 사용하면 값 타입 컬렉션처럼 사용할 수 있다.
사용하는 방법은 @ElementCollection을 이용해서 컬렉션을 매핑하면 된다.
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
@ElementCollection
@CollectionTable(name = "order_items")
private List<OrderItem> items = new ArrayList<>();
// 다른 필드, 생성자, getter, setter 등
}
@Embeddable
public class OrderItem {
private String name;
private BigDecimal price;
}
데이터베이스
[OrderItem]
- ORDER_ID (PK)
- NAME (PK)
- PRICE (PK)
데이터베이스 모든 컬럼값에 PK 가 걸리기 때문에 NAME, PRICE 값 중 하나라도 수정하게 되면 PK를 찾지 못한다.
따라서 OrderItem 엔티티 안에 있는 값을 수정하기 위해서는 DELETE & INSERT를 해야 한다.
데이터를 많이 수정할 경우 성능이 떨어진다.
위에 정리한 값 타입 컬렉션 ERD를 보면 값 타입 컬렉션 내부의 컬럼들이 모두 PK로 설정돼있다. 그런데 실제 사용하는 프로젝트의 DB를 보면 ACollection 컬럼들에는 PK가 없다.
@Entity
public class A {
....
@Builder.Default
@ElementCollection
@CollectionTable(name = "A_Collection", joinColumns = @JoinColumn(name = "id"))
@OrderColumn(name = "list_index")
private List<ACollection> aCollection = new ArrayList<>();
}
@Embeddable
public class ACollection {
private String name;
private String content;
....
}
데이터베이스
[A]
- A_ID (PK)
- LIST_INDEX (PK)
- NAME
- CONTENT
인프런 질문 에서는 @OrderColumn을 사용해서 이런 차이가 발생한다고 나와있다.
@OrderColumn은 값 타입 컬렉션을 최적화하는 방법이다.
기존 값 타입 컬렉션에서 값을 수정하면 Delete & Insert 가 발생했는데
@OrderColumn 방법은 Update만 발생한다.
이는 NAME, CONTENT 값을 변경해도 PK (A_ID, LIST_INDEX)로 특정 행을 구분할 수 있기 때문이다.
정리
참고자료
- https://www.yes24.com/Product/Goods/19040233 "자바 ORM 표준 JPA 프로그래밍-김영한"
'Coding > computer science' 카테고리의 다른 글
JPA에서 Page 기능 사용하기 (0) | 2023.11.23 |
---|---|
자바 ORM 표준 JPA 프로그래밍 | 10. 객체지향 쿼리 언어 (0) | 2023.11.16 |
자바 ORM 표준 JPA 프로그래밍 | 08. 프록시와 연관관계 관리 (0) | 2023.10.19 |
자바 ORM 표준 JPA 프로그래밍 | 07. 고급 매핑 (0) | 2023.10.05 |
자바 ORM 표준 JPA 프로그래밍 | 06. 다양한 연관관계 매핑 (0) | 2023.09.18 |