*김영한님의 JPA 기본편을 기반으로 작성하였습니다.
JPA의 데이터 타입 분류
엔티티 타입
- @Entity로 정의하는 객체
- 데이터가 변해도 식별자로 지속해서 추적 가능
- 예)회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능
값 타입
- int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
- 식별자가 없고 값만 있으므로 변경시 추적 불가
- 예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체
기본값 타입
자바 기본 타입(int, double) • 래퍼 클래스(Integer, Long)
String
임베디드 타입(embedded type, 복합 값 타입): 좌표값 등을 묶어서 값으로 쓰고 싶을 때, x,y좌표를 클래스로 묶어 값처럼 사용
컬렉션 값 타입(collection value type): 기본값, 임베디드 값 타입을 자바 컬렉션 처럼 넣을 수 있는 타입
기본 값타입: - 생명주기를 엔티티에 의존(회원 삭제시, 이름, 나이 필드도 같이 삭제됨)- 값타입은 공유하면 안된다.( 회원이름 변경 시, 다른 회원 이름도 변경되면 안된다). 또한 기본 타입은 값을 복사하는 것이기에 공유가 되지 않는다.
Integer, String 같은 특수한 클래스는 공유 가능한 객체지만(래퍼런스 포함), 하지만 값을 변경하는 것이 불가하기에 변경되지 않음.
임베디드 타입
- 새로운 값 타입을 직접 정의할 수 있음
- JPA는 임베디드 타입(embedded type)이라 함
- 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 함 int, String과 같은 값 타입
-int, String과 같은 값타입
![](https://blog.kakaocdn.net/dn/tJDwQ/btr24LMNT3z/W8WfFPvYqayMkpsrHwPKp0/img.png)
회원 엔티티는 집 주소를 가진다 -> 집 주소: 나라, 동, 단지, 등등 여러가지를 임베디드 타입으로 묶어서 가능.
![](https://blog.kakaocdn.net/dn/eaJlJE/btr24LlJvBA/wWfaDRrBVe5doa0KYY30SK/img.png)
위의 예시에서는 Period, Address라는 임베디드 타입을 만들어서 묶어놓은 것.
- @Embeddable: 값 타입을 정의하는 곳에 표시 기본 생성자 필수
- @Embedded: 값 타입을 사용하는 곳에 표기
- 기본 생성자 필수임베디드 타입 장점
- 재사용
- 높은 응집도
- Period.isWork()처럼 해당 값 타입만 사용하는 의미 있는 메소드를 만들 수 있음
- 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티 티에 생명주기를 의존함
임베디드 타입과 테이블 매핑
![](https://blog.kakaocdn.net/dn/bP2YIj/btr24jpu7Lj/rS5oPJMNThV6mGKasF3cT0/img.png)
클래스로 만들지만, 실제 값이 테이블에 들어갈 때는, 속성으로 들어가게 된다.
예시)
시작일, 종료일이라는 두가지 필드를 임베디드 타입으로 만들기
private Date startDate;
private Date endDate;
정의
@Embeddable
public class Period {
private Date startDate;
private Date endDate;
public Period() {//기본 생성자 필수
}
}
사용
@Embedded
private Period period;
이제 member.setPeriod(new Period(startTime,endTime) ); 이런식으로 사용 가능 ( 생성자 필수인 이유 )
만약 한 엔티티에서 같은 값 타입을 여러개 사용하면?
ex) 멤버에 집 주소, 그리고 본가 집 주소 2가지의 Address(임베디드 타입) 정의 시
- 컬럼 명이 중복됨
- @AttributeOverrides, @AttributeOverride를 사용해서 컬러 명 속성을 재정의
+ 임베디드 타입의 값이 null이면 매핑한 컬럼 값은 모두 null
회원 1,2는 서로 다른 테이블에서 City라는 임베디드 타입을 가지고 있지만, 둘 중하나가 City라는 임베디드 타입을 바꾸면 나머지도 전부 바뀌게 된다.
값 타입의 실제 인스턴스인 '값' 을 공유하는 것은 위험하기에, 그 대신에 인스턴스를 복사해서 사용한다.
하지만, 이러면 누군가 실수로 그냥 쓰더라도 컴파일 레벨에서 문제를 잡을 수 없다.
변하지 않는 '불변 객체' 를 사용하자 -> 클래스의 setter을 모두 지우고 생성자로 값 세팅하거나 필드를 private으로 선언하면 된다.
만약 값을 바꾸고 싶다면? -> new 로 새로운 객체를 만들어서 다시 임베디드 타입을 넣어줘야한다. ( 바꾸고 싶은 부분만 setter로 바꾸는게 아닌, new로 새롭게 만들어서 통으로 바꿔 낀다)
- 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단
- 값 타입은 불변 객체(immutable object)로 설계해야함
- 불변 객체: 생성 시점 이후 절대 값을 변경할 수 없는 객체
- 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 됨 •
참고: Integer, String은 자바가 제공하는 대표적인 불변 객체
정리: 값타입은 불변으로 만들어야한다. 임베디드 타입( 클래스로 여러 타입 묶어 새로운 타입 정의) 같은 경우,Setter없이 생성자로 필드를 채우거나 필드에 private을 걸어 불변으로 만들어야한다.
[값 타입의 비교]
![](https://blog.kakaocdn.net/dn/bUlOd3/btr29Ibp9On/e83HMRwSqM1oWMQpYAiNOk/img.png)
객체 타입은 값이 같아도 == 비교시 false가 나온다.
동등성(equivalence) 비교: 인스턴스의 값을 비교: equals()를 오버라이드 해서 사용해야한다( equals는 기본 == 비교)
인텔리제이의 경우 해당하는 임베디드 타입 클래스로 가서 컨트롤+ shift 로 equals()를 선택하고 자동 생성 가능.![](https://blog.kakaocdn.net/dn/dHusTj/btr3aA45Bs7/juc7AEGdtXmeMK4tjDurak/img.png)
값 타입은 a.equals(b)를 사용해서 동등성 비교를 해야 함
값 타입의 equals() 메소드를 적절하게 재정의(주로 모든 필드 사용)
값 타입 컬렉션
- 값 타입을 하나 이상 저장할 때 사용 ( ex: 주문 음식 리스트: "Food"라는 임베디드 타입 여러개를 List에 담아서 저장하는 경우)
- @ElementCollection, @CollectionTable 사용
- 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
- 컬렉션을 저장하기 위한 별도의 테이블이 필요함
Member라는 엔티티에 몇가지 값 타입 컬렉션을 추가해보자.
![](https://blog.kakaocdn.net/dn/tsdji/btr3a63KDl8/63i6IFNdwhpAVE2FTuJqm0/img.png)
-> Address, String 등 객체 타입을 여러개 저장 시, 별도의 테이블을 생성해서 관리해야됨.
위의 어노테이션을 통해 Favorite_food 라는 테이블이 생성해 String 타입 컬렉션을 관리하고, ADDRESS 라는 테이블이 생성되어
Address의 컬렉션을 관리함
결과
값타입 컬렉션에 대한 별도의 테이블이 생성되어 Member와 관계를 이룬다.
추가로, 값타입 컬렉션 역시 "값 타입" 이기에, 엔티티 클래스의 생명주기를 따르고, 그렇기에
Member가 perisist되면 Favorite_food, ADDRESS 도 persist 되고,
삭제되면 같이 삭제됨.
-> 값 타입 컬렉션은 영속성 전에(Cascade) + 고아 객체 제 거 기능을 필수로 가진다고 볼 수 있다.
만약 컬렉션 내의 값타입을 바꾸고 싶다면?값타입( String, 임베디드 타입 등)은 불변이여야한다. favoriteFood.remove(바꿀 값 ), favoriateFood.add(바뀐 값) 이런식으로 지우고 새로 넣어줘야한다.
ex) 컬렉션 내의. old1 시티의 street 거리, 10000이라는 zipcode를 가진 값 타입을 변경하고 싶다면, 지우고 다시 넣어줘야한다.
**주의: 바꿀값을 remove() 하고 add() 하는 것에서, 아마 값 타입 컬렉션 내에서 "바꿀 값인 old1~~~만 지우고 새롭게 넣는다" 라고 생각할 수 있지만, 실제론
값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.( old1~~~ 만 지우고 add 했지만 실제론 old1,old2,...... 컬렉션 내 모든 값 타입들이 테이블에서 사라졌다 다시 저장되는 것.
값 타입 컬렉션의 제약사항- 값 타입은 식별자 개념이 없다.- 값은 변경시 추적이 어렵다.
- 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어 P.K를 구성한다 ( 따라서 null 입력, 혹은 중복 저장 불가)
어차피 값 타입은 "값" 자체만 있고, 식별자(P.K. Id등) 가 없기에, 이렇게 함으로써 제약조건을 거는 것.(내부 필드= 불변 (식별의 역할을 수행하기에 바뀌면 다른게 되어버림),
이러한 제약 때문에, 대안이 존재.
실무에서는 상황에 따라 값 타입 컬렉션 대신에 엔티티 승격 후 일대다 관계를 고려
(단, 일 대 다 관계는 외래키가 "다" 쪽에 있고, 주인이 One쪽이기에, update가 값 타입 마다 나가게 되는 문제가 있다.
경우에 따라 ManyToOne 양방향 매핑을 통해 반대 방향의 OneToMany를 활용해도 무방하다.)
영속성 전이(Cascade) + 고아 객체 제거를 사용해서 값 타입 컬 렉션 처럼 사용
EX) Address에 @Id 로 식별자를 만들고,
member에서
![](https://blog.kakaocdn.net/dn/bbun6A/btr22M6UIv2/RhTiMK6Bx5Z2YMPktn6LD0/img.png)
다음과 같이 OneToMany 관계를 만들어서 저장해줘도 된다.
(값타입이 아닌, 엔티티로 승격시키는 방법)
일 대 다 관계로 엔티티를 만들어 값을 만드는 법 요약1) 엔티티 생성: setter를 만들지 않거나 private으로 만들어 값의 "불변" 특성을 만들기
2)정의 엔티티에는 @Embeddable, 사용하는 쪽에는 @Embedded ( 안해도 되지만 값타입으로써 사용되는 엔티티임을 명시하기 위함)3) equals() 함수를 오버라이딩해서 ==식별자가 아닌, 제대로된 비교함수 형성( 엔티티는 참조값도 있기에 필드 값이 전부 같다고==연산자가 통하지 않는다) -> 만약 값 업데이트, 제거시 remove() 함수를 사용하면, 비교연산자를 통해 지우려는 값을 찾고 지우기에 중요하다!
값타입으로써 사용할 엔티티 클래스에서 control+enter 로 equals(),hash() 함수를 선택하여 오버라이딩 후
use getters during code generation 속성을 체크하고 생성해준다.
그러면 값 타입 컬렉션은 언제 쓸까?
-> "엄청 엄청 단순한 경우" 추적할 필요도 없고, 값이 바뀌어도 update 할 필요 없을 때 등.
정리
엔티티 타입의 특징
- 식별자O
- 생명주기관리
- 공유
값타입의특징
- 식별자X
- 생명 주기를 엔티티에 의존
- 공유하지 않는 것이 안전(복사해서 사용)
- 불변 객체로 만드는 것이 안전
값 타입은 정말 값 타입이라 판단될 때만 사용 (포지션, {x,y} 좌표 등등
엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨
식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것 은 값 타입이 아닌 엔티티
'Spring boot' 카테고리의 다른 글
[JPA] Part 6 (객체지향 쿼리 언어 pt.2) (0) | 2023.03.13 |
---|---|
[JPA] Part 5 (객채지향 쿼리 언어 pt.1) (0) | 2023.03.13 |
[JPA] Part 3 프록시와 연관관계 (0) | 2023.03.10 |
[JPA] Part2 (심화 매핑) (0) | 2023.03.10 |
[JPA 기본] Part1 (영속성 컨텍스트, 기본 매핑 ) (0) | 2023.03.08 |