JPA의 데이터 타입은 크게 두 가지로 분류할 수 있다.
- 엔티티
- 값타입
엔티티는 @Entity로 정의한 객체이다.
값타입은 int, Integer, String 같은 기본 타입과 객체를 의미한다.
값타입을 조금 더 자세히 분류하면 세 가지가 나온다.
- 기본값 타입 (int, String, Integer)
- 임베디드 타입 (사용자 정의 값 : Address, Name, ...)
- 컬렉션 값 타입 (하나 이상의 값타입)
이제부터 기본값 타입, 임베디드 타입, 컬렉션 값 타입에 대해서 하나하나 알아보자.
🟩 기본값 타입 (Basic Value Type)
- int, double 과 같은 기본 값 타입
- Integer, String과 같은 래퍼 타입이 있다.
기본값 타입은 이전 예제부터 자주 사용하고 있었다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
private int age;
}
DDL은 다음과 같이 생각대로 잘 나간다.
create table member (
age integer not null,
id bigint not null,
name varchar(255),
primary key (id)
)
🟩 임베디드 타입 (Embedded Type)
사용자가 새로운 값 타입을 정의해서 사용할 수 있다.
JPA에서는 이를 임베디드 타입이라고 부른다.
위 기본값 타입 예제에서 필드 String name과 int age를 VO로 만들어서 임베디드 타입을 이용해보자.
01. @Embeddable 어노테이션
값 객체가 엔티티에 참조될 수 있다고 선언해주자.
@Embeddable
public class Name {
private String name;
}
02. @Embdedded 어노테이션
값 객체를 이용할 엔티티의 필드에 선언해주자.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Embedded
private Name name;
}
03. 임베디드 타입 매핑 예시 코드
- 회원의 이름(name)과 나이(age)를 값객체로 사용한다.
- @Embeddable 혹은 @Embedded 둘 중 하나만 선언되어도 상관없다.
@Embeddable
public class Name {
private String name;
}
public class Age {
private int age;
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private Name name;
@Embedded
private Age age;
}
DDL도 잘 만들어졌음을 확인할 수 있다.
create table member (
age integer not null,
id bigint not null,
name varchar(255),
primary key (id)
)
04. @Embeddable과 @Embedded 둘 다 없을 경우 예외가 발생한다.
- Name 값객체에 @Embeddable과 @Embedded 선언이 둘 다 되지않아서 JPA에서 타입을 인식할 수 없다.
public class Name {
private String name;
}
public class Age {
private int age;
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private Name name;
@Embedded
private Age age;
}
Caused by: org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException: Could not determine recommended JdbcType for `com.example.jpa.domain.Name`
05. 값객체 내부에 엔티티를 참조하고 있을 수 있다.
- 값 객체 내부에 엔티티를 참조하고 있을 수 있다.
05-01. 값객체 내부 엔티티 매핑 코드
- 값 객체인 Age 내부에 엔티티 AgeType이 존재한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Embedded
private Name name;
@Embedded
private Age age;
}
@Embeddable
public class Age {
private int age;
@ManyToOne
@JoinColumn(name = "age_type")
private AgeType ageType;
}
@Entity
public class AgeType {
@Id @GeneratedValue
private Long id;
private String type;
}
@Embeddable
public class Name {
private String name;
}
DDL은 다음과 같이 잘 생성된다.
create table age_type (
id bigint not null,
type varchar(255),
primary key (id)
)
create table member (
age integer not null,
age_type bigint,
id bigint not null,
name varchar(255),
primary key (id)
)
06. 같은 타입의 값객체를 여러개 갖을 때 @AttributeOverride를 통해 속성을 재정의한다.
- 같은 타입의 값객체를 여러개 갖을 경우 컬럼명이 중복될 수 있다.
아래와 같이 Member 엔티티가 homeAddress과 companyAddress을 가지도록 매핑해보자.
06-01. Name 속성을 재정의하지 않았을 때
- 필드명을 다르게 둔다해도 예외가 발생한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Embedded
private Address homeAddress;
@Embedded
private Address companyAddress;
}
Caused by: jakarta.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Column 'city' is duplicated in mapping for entity 'com.example.jpa.domain.Member' (use '@Column(insertable=false, updatable=false)' when mapping multiple properties to the same column)
회원이 "username"과 "nickname"을 갖는다고 할 때 속성을 재정의해서 컬럼명이 중복되지 않게 해야한다.
06-02. Name 속성을 재정의 했을 때
- @AttributesOverride 어노테이션을 사용해서 Address 내부 필드의 컬럼명을 지정해주자.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Embedded
private Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "company_city")),
@AttributeOverride(name = "street", column = @Column(name = "company_street"))
})
private Address companyAddress;
}
DDL도 원하는데로 이쁘게 잘 나오는 것을 확인할 수 있다.
create table age_type (
id bigint not null,
type varchar(255),
primary key (id)
)
create table member (
id bigint not null,
city varchar(255),
company_city varchar(255),
company_street varchar(255),
street varchar(255),
primary key (id)
)
🟩 값 타입 컬렉션 (@ElementCollection, @CollectionTable)
값타입을 컬렉션 형태로 이용해보자.
아래와 같이 회원이 이름 컬렉션과 주소 컬렉션을 갖도록 구현해보자.
그 전에 @ElementCollection, @CollectionTable에 대해서도 알아보자.
01. @ElementCollection 어노테이션
- 기본값 타입 혹은 임베디드 타입의 인스턴스들의 컬렉션임을 의미한다.
01-01. @ElementCollection 만 사용할 경우 예외가 발생한다.
- 데이터베이스에서 한 테이블로 컬렉션을 가지고 있는 상태를 표현할 수 없기 때문이다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@CollectionTable
private List<Name> names = new ArrayList<>();
}
Caused by: org.hibernate.AnnotationException: Property 'com.example.jpa.domain.Member.names' is mapped as basic aggregate component array, but this is not yet supported.
02. @CollectionTable 어노테이션
@ElementCollection만 이용했을 때의 예외를 @CollectionTable을 이용해서 해결할 수 있다.
@CollectionTable을 이용하면 컬렉션을 위한 테이블을 하나 생성해서 해결한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ElementCollection
@CollectionTable
private List<Name> names = new ArrayList<>();
}
DDL을 보면 새로운 member_names 테이블이 만들어지는 것을 알 수 있다.
create table member (
id bigint not null,
primary key (id)
)
create table member_names (
member_id bigint not null,
name varchar(255)
)
02-01. member_names 테이블명을 수정할 수 있다.
@CollectionTable의 name 속성을 이용하면된다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ElementCollection
@CollectionTable(name = "member_name")
private List<Name> names = new ArrayList<>();
}
create table member (
id bigint not null,
primary key (id)
)
create table member_name (
member_id bigint not null,
name varchar(255)
)
03. 값 타입 컬렉션 매핑 예시 코드
- 회원은 하나 이상의 이름과 하나 이상의 주소를 가질 수 있다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ElementCollection
@CollectionTable(name = "member_name")
private List<Name> names = new ArrayList<>();
@ElementCollection
@CollectionTable(name = "member_address")
private List<Address> addresses = new ArrayList<>();
}
@Embeddable
public class Name {
private String name;
}
@Embeddable
public class Address {
private String city;
private String street;
}
create table member (
id bigint not null,
primary key (id)
)
create table member_address (
member_id bigint not null,
city varchar(255),
street varchar(255)
)
create table member_name (
member_id bigint not null,
name varchar(255)
)
🟩 "값 타입 컬렉션"말고 "일대다 관계"를 이용하자.
변경할 때 있어서 값 타입 컬렉션과 엔티티의 차이는 클 수 있다.
01. 값 타입 컬렉션 내부 값 변경할 경우
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ElementCollection
@CollectionTable(name = "member_address")
private List<Address> addresses = new ArrayList<>();
}
@Embeddable
public class Address {
private String city;
private String street;
}
식별자값이 10인 회원이 3개의 주소(address1, address2, address3)를 가지고 있다고 하자.
이 때 회원이 address3을 수정한다고 한다.
여기서 UPDATE SQL 1개만 전송하면 좋겠지만 사실 DELETE SQL 3개 + INSERT SQL을 3개를 전송해버린다.
DELETE FROM ...
DELETE FROM ...
DELETE FROM ...
INSERT INTO ...
INSERT INTO ...
INSERT INTO ...
이유는 회원이 소유한 주소들 중에 address3은 식별자값이 없어서 UPDATE 할 부분을 찾기 힘들어한다.
때문에 식별자값이 10인 회원의 모든 주소를 DELETE하고 다시 모든 주소를 INSERT하는 식으로 진행하게 되어 성능이 떨어지게 된다.
02. 일대다 관계 + 영속성 전이 + 고아 객체 제거를 이용하자.
값 객체 컬렉션을 이용하는 것보다 Address를 엔티티로 만들어서 이용하는 편이 좋다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "member_id")
private List<Address> addresses = new ArrayList<>();
}
@Entity
public class Address {
@Id @GeneratedValue
private Long id;
private String city;
private String street;
}
create table address (
id bigint not null,
member_id bigint,
city varchar(255),
street varchar(255),
primary key (id)
)
create table member (
id bigint not null,
primary key (id)
)
🔗 참고
자바 ORM 표준 JPA 프로그래밍 - YES24
자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA 기초 이론과 핵심 원리, 그리고
www.yes24.com
'😋 JPA' 카테고리의 다른 글
일단 테스트 통과시키기 (count 타입 체크, 일급 컬렉션 주소, Page.getContent()) (0) | 2023.08.13 |
---|---|
[JPA] 영속성 전이, 고아 객체 (Cascade, Orphan) (0) | 2023.06.16 |
[JPA] 프록시, 즉시 로딩, 지연 로딩 - (FetchType.EAGER, FetchType.LAZY) (0) | 2023.06.16 |
[JPA] 고급 매핑 - 테이블 매핑 정보 상속, 엔티티 매핑 정보 상속, 복합키, 식별 관계, 비식별 관계 (@Inheritance, @MappedSuperclass, @IdClass, @EmbeddableId) (0) | 2023.06.16 |
[JPA] 다양한 연관관계 매핑 - 다대일, 일대다, 일대일, 다대다 연관관계 (0) | 2023.06.15 |