본문 바로가기
프로그래밍/Back-end

JPA 데이터 타입과 기본 임베디드 컬렉션에 대하여!

by @GodWin 2024. 10. 14.

-

 

-
안녕하세요? 오늘은 JPA에서의 데이터 타입 분류에 대해서 알아보도록 하겠습니다.


JPA에서의 데이터 타입은, 엔티티 타입과, 값 타입으로 분류합니다.


엔티티 타입

: @Entity로 정의하는 객체
: 식별자가 존재
: 생명주기 관리 가능
: 공유 가능
: 데이터가 변해도,식별자로 지속해서 추적 가능




값 타입

: 의미있는 비지니스 메소드 생성 사용 가능
: 식별자 미존재
: 공유하지 않고, 복사해서 사용
: 불변 객체로 만들어서 사용 (getter 생성, setter 미생성)
: int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
: 복잡한 객체 세상을 단순화하기 위한 개념
> 단순하고 안전하게 다룰 수 있어야한다.
>> 엔티티를 값 타입으로 만들면 X
>>> 식별자가 필요하고, 지속해서 값을 추적하고, 값을 변경해야 한다면, 값 타입이 아닌 엔티티 사용




※ 모든 값 타입은, 생명주기를 엔티티에 의존


+
값 타입에서는 기본 값 타입 임베디드 값 타입 컬렉션 값 타입 세가지 타입이 존재합니다.

++
기본 값 타입
(primitive type)

: 자바가 제공하는 값 세팅 기본 타입
: 생명주기를 엔티티에 의존




※ 값 타입은 서로 값을 공유해서는 안된다.
> 부작용 발생 (Side effect)

기본 타입 int, double ... 공유 불가, 값을 복사하는 개념
래퍼 클래스 Integer, Long ... 공유 가능, 주소를 참조하는 개념, 변경 불가
특수 클래스 String ...

 

++
임베디드 타입
(embedded type)

 

: 복합 값 타입
: 새로운 값 타입 직접 정의 가능
: 기본 값 타입을 모아서 생성
: int, String과 같은 값 타입
: 재사용 가능
: 높은 응집도
: 의미있는 메소드 생성 가능
: 객체와 테이블의 아주 세밀한(Find-grained) 매핑 가능
: 하나의 엔티티에서 값은 값 타입의 중복 사용 불가
> AttributeOverrides 사용으로 해결 가능




※ 임베디드 타입은 엔티티의 값일 뿐임을 명심

+++
사용 방법

: @Embeddedable
> 값 타입을 정의하는 곳에 표시
: @Embedded
> 값 타입을 사용하는 곳에 표시
: 기본 생성자 필수



EntityA 엔티티 생성)

@Entity
public class EntityA {
   
   @Id @GeneretedValue
   @Column(name = "ENTITYA_ID")
   private Long id;
   
   @Column(name = "ENTITYA_NAME")
   private String name;
   
   // Item1 + Item2
   private LocalDateTime item1;
   private LocalDateTime item2;
   
   // Item3 + Item4
   private String item3;
   private String item4;
   
   ...
   
}


해당 EntityA 엔티티에서,
Item3 , Item4 관련 항목들을,
임베디드 타입으로 변경하게 되면 아래와 같다.

EntityA 엔티티 생성)

@Entity
public class EntityA {
   
   @Id @GeneretedValue
   @Column(name = "ENTITYA_ID")
   private Long id;
   
   @Column(name = "ENTITYA_NAME")
   private String name;
   
   // Item1 + Item2
   private LocalDateTime item1;
   private LocalDateTime item2;
   
   // Item3 + Item4
   @Embedded
   private Item34 item34;
   
   ...
   
}


Item34 관련 임베디드 값 타입 생성)

@Embeddable
public class Item34 {
  
  private String item3;
  private String item4;
  
  ...
  
}


샘플코드)

EntityManagerFactory emf = Persistence.createEntityManagerFactory("유닛명");
EntityManager em = emf.createEntityManager();

EntityTransaction tx = em.getTransaction();
tx.begin();

Item34 item34 = new Item34( item3: "item3_1", item4: "item4_1");

EntityA entityA = new EntityA();
entityA.setName("entityA_1");
entityA.setItem34(item34);
em.persist(entityA);

tx.commit();



※ 값 타입의 실제 인스터스인 값을 공유하는것은 위험
: Side effect 발생
> 대신 값을 복사해서 사용

샘플 코드)

Item34 item34 = new Item34( item3: "Item3_1", item4: "Item4_1");

EntityA entityA1 = new EntityA();
entityA1.setName("entityA_1");
entityA1.setItem34(item34);
em.persist(entityA1);

Item34 copyItem34 = new Item34( item34.getItem3(), item34.getItem4());

EntityA entityA2 = new EntityA();
entityA2.setName("entityA_2");
entityA2.seItem34(copyItem34);
em.persist(entityA2);

tx.commit();


※ 직접 정의한 값 타입은,
자바의 기본 타입이 아니라 객체 타입
> 객체 타입은 공유 참조를 피할 수 없다.
> 불변 객체로 설계 / 생성 (getter 생성, setter 미생성)
(Immutable object)

기본 타입 (primitive type) < - > 객체 타입
기본 타입은 값을 복사

ex)
int a = 10;
int b = a;
a = 20;
-> b 값은 미변경
객체 타입은 참조를 전달

ex)
Item34 a = new Item34("Old");
ITEM34 b = a;
a.setItem3("New");
-> b 값도 New로 변경



++
- 컬렉션 값 타입
(collection value type)

: 추적할 필요가 없고, 데이터 수정이 필요 없는 상황에서 주로 사용
: 자바 컬렉션에, 기본 값 / 임베디드 타입을 넣어서 사용
: DB는 컬렉션을 같은 테이블에 저장 불가
> 컬렉션을 저장하기 위한 별도의 테이블 필요
: 식별자가 없고 값만 있으므로 변경시 추적이 불가
> 관련된 데이터 전부 삭제 후, 최종 데이터 등록
>> 매핑하는 테이블은 모든 컬럼을 묶어서, 기본 키를 구성해야 한다. (Not Null / 중복 X : 모든 테이블 컬럼이 PK로 사용)
>>> 해당 기능 대신, 일대다 [1:N] 관계를 고려




※ 컬렉션 값 타입은, 영속성전이 CASCADE와 고아객체 제거 기능을 필수로 가진다.


+++
사용법

@ElementCollection
@CollectionTable


EentityA 엔티티 생성)

@Entity
public class EntityA {
  
  @Id @GeneretedValue
  @Column(name = "ENTITYA_ID")
  private Long id;
  
  @Column(name = "ENTITYA_NAME")
  private String name;
  
  @Embedded
  private Item34 item34;
  
  @ElementCollection
  @CollectionTable(name = "ITEMS",
      joinColumns = @JoinColumn(name = "ENTITYA_ID"))
  @Column(name = "ITEM_NAME")  // 예외적 허용
  private Str<String> items = new HashSet<>();
  
  @ElementCollection
  @CollectionTable(name = "ITEM34_H",
      joinColumns = @JoinColumn(name = "ENTITYA_ID"))
  private List<Item34> item34History = new ArrayListM<>();
  
  ...
  
}


Items 임베디드 값 타입 생성)
: entityA 엔티티에서
ITEM_NAME 외 사용 항목이 없기 때문에,
별도 생성 불필요

ITEM34 임베디드 값 타입 생성)

@Embeddable
public class Item34 {
  
  private String item3;
  private String item4;
  
  ...
  
}


샘플코드)

EntityManagerFactory emf = Persistence.createEntityManagerFactory("유닛명");
EntityManager em = emf.createEntityManager();

EntityTransaction tx = em.getTransaction();
tx.begin();

Item34 item34 = new Item34(item3: "ITEM3_1", item4: "ITEM4_1");

EntityA entityA = new EntityA();
entityA.setName("entityA_1");
entityA.setItem34(item34);

entityA.getItems().add("items_1");
entityA.getItems().add("items_2");
entityA.getItems().add("items_3");

entityA.getItem34History().add(new Item34(item3: "ITEM3_2", item4: "ITEM4_2"));
entityA.getItem34History().add(new Item34(item3: "ITEM3_3", item4: "ITEM4_3"));

em.persist(entityA);

em.flush();
em.close();

EntityA findEntityA = em.find(EntityA.class, entityA.getId());

// 지연로딩
Set<String> items = findEntityA.getitems();
for (String item : items) {
  System.out.println("item : " + item);
}

// 지연로딩
List<Item34> item34History = findEntityA.getItem34History();
for (Item34 item34 : item34History) {
  System.out.println("item34 : " + item34.getItem34());
}

// 일부 데이터만 삭제 후 신규 등록
findEntityA.getItems().remove( o: "items_1");
findEntityA.getItems().add("items_4");

// 테이블 내용을 통채로 삭제 후 등록
findEntityA.getItem34History.remove(new Item34(item3: "ITEM3_1", item4: "ITEM4_1"));
findEntityA.getItem34History.add(new Item34(item3: "ITEM3_4", item4: "ITEM4_4"));

tx.commit();


DB 결과) ENTITYA

ENTITYA_ID ENTITYA_NAME ITEM3 ITEM4
1 entityA_1 ITEM3_1 ITEM4_1


DB 결과) ITEMS

ENTITYA_ID ITEMS
1 items_1
1 items_2
1 items_3
1 items_4


DB 결과) ITEM34_H

ENTITYA_ID ITEM3 ITEM4
1 ITEM3_1 ITEM4_1
1 ITEM3_2 ITEM4_2
1 ITEM3_3 ITEM4_3
1 ITEM3_4 ITEM4_4

 

데이터 타입의 비교

: 인스턴스가 달라도,
그 안에 값이 같으면 같은것으로 봐야 함

기본 타입 < - > 객체 타입
int a = 10;
int b = 10;
-> a == b : true로 사용 가능
->> 동일성 비교(identity)
Items a = new Items("items_4");
Items b = new Items("items_4");
-> a == b : false 사용 불가
(인스턴스 자체가 다르기 때문)
->> a.equals(b) 사용
->>> 동등성 비교(equivalence)




오늘은 JPA에서의 데이터 타입 분류에 대해서 알아보았습니다.
그럼 오늘도 즐거운 하루 되시길 바라겠습니다.