김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.
https://www.inflearn.com/courses?s=%EA%B9%80%EC%98%81%ED%95%9C
JPA는 엔티티 타입과 기본값 타입으로 데이터 타입을 분류할 수 있습니다.
엔티티 타입
- @Entity로 정의하는 객체
- 데이터가 변해도 식별자로 지속해서 추적 가능
- 예) 회원 엔티티의 키, 나이 값을 변경해도 식별자로 인식가능
값 타입
- int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
- 식별자가 없고 값만 있으므로 변경시 추적 불가
- 예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체
또한 값타입은 다시 아래와 같이 나눌 수 있습니다.
값 타입 분류
- 기본값 타입 : 1. 자바 기본 타입(int, double) 2.래퍼 클래스(Integer, Long) 3. String
- 임베디드 타입(embedded type, 복합 값 타입)
- 컬렉션 값 타입(collection value type)
값 타입들을 하나씩 알아보겠습니다.
목차:
● 기본값 타입
● 임베디드 타입(+불변객체)
● 값타입 컬렉션
● 기본값 타입
기본 값 타입에는 String name, int age와 같은 것들이 있습니다.
• 생명주기가 엔티티에 의존 -> 회원을 삭제하면 이름, 나이 필드도 함께 삭제됩니다. 값 타입은 공유하면 안됩니다.
• 값 타입은 공유하면 안됨 -> 예) 회원 이름 변경시 다른 회원의 이름도 함께 변경되면 안됩니다.
이는 자바의 기본 타입과 같습니다.
• int, double 같은 기본 타입(primitive type)은 절대 공유X
• 기본타입은 항상 값을 복사함
• Integer같은 래퍼 클래스나 String 같은 특수한 클래스는 공유 가능한 객체이지만 변경X
● 임베디드 타입(복합 값 타입)
• 새로운 값 타입을 직접 정의할 수 있음
• JPA는 임베디드 타입(embedded type)이라 함
• 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 함
• int, String과 같은 값 타입
회원 엔티티가 아래와 같은 필드들을 가진다고 해봅시다.
위의 startDate와 endDate를 근무기간으로. city, street, zipcode를 집주소로 하나의 class로 묶듯이 JPA에서도 이러한 기능을 제공합니다.
사용법은 아래와 같습니다.
@Embeddable: 값 타입을 정의하는 곳에 표시
@Embedded: 값 타입을 사용하는 곳에 표시
기본 생성자 필수
코드로 보겠습니다.
위의 Member 엔티티에 임베디드 타입을 사용하면 아래와 같습니다.
임베디드 타입 Address
임베디드 타입 Period
위에서 만든 임베디드 타입들과 테이블은 아래 그림처럼 매핑됩니다.
- 임베디드 타입은 엔티티의 값일 뿐이다.
- 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.
- 객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가능
- 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많음
이제 아래와 같이 Member를 생성하고 address와 period를 set하고 실행해보겠습니다.
create DDL이 아래와 같이 나가는 것을 볼 수 있습니다.
테이블에는 임베디드를 적용하기 전과 똑같이 저장됩니다.
이렇게 임베디드를 사용하면 아래와 같은 장점이 있습니다.
1. 재사용
2. 높은 응집도
3. Period.isWork()처럼 해당 값 타입만 사용하는 의미 있는 메소드를 만들 수 있음
4. 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명주기를 의존함
아래처럼 각 임베디드 타입 객체에 특화된 메소드를 만들어 사용하기 편리합니다.
@AttributeOverride: 속성 재정의
또한 하나의 엔티티에서 같은 값 타입을 사용하기 위해 @AttributeOverride를 제공합니다.
예를 들어 Member에 현재 homeAddress가 있는데, 직장 주소인 workAddress도 추가하고 싶다면 아래와 같이 하면 됩니다.
+ 값 타입과 불변 객체
값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념입니다. 따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 합니다.
값 타입 공유 참조
• 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험합니다.
• 부작용(side effect) 발생합니다.
발생할 수 있는 부작용을 코드로 살펴보겠습니다.
위의 member1과 member2는 같은 값타입을 공유하고 있습니다(같은 address를 set). 위의 코드를 실행하면 두번의 insert쿼리가 나가고, 테이블은 아래와 같을 것입니다.
member1과 member2가 같은 city, street, zipcode를 가짐.
그런데 member1의 city만 바꾸고 싶어 아래와 같은 코드를 실행한다면 어떻게 될까요?
현재 같은 address를 공유하고 있기 때문에 member2의 city도 변경됩니다.
두번의 update쿼리가 나가는 것을 볼 수 있습니다.(member1, member2의 city를 바꾸기 위해)
테이블을 보면 확실히 member1, member2의 city가 변경되었습니다.
위와 같은 부작용은 두개의 member엔티티가 같은 값타입의 참조를 공유하고 있기 때문에 발생합니다.
• 값 타입의 실제 인스턴스인 값을 공유하는 것은 위험
• 대신 값(인스턴스)를 복사해서 사용
이것은 객체타입의 한계입니다. 하여 위의 그림처럼 복사해서 사용해야 합니다.
- 항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다.
- 문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입이다.
- 자바 기본 타입에 값을 대입하면 값을 복사한다.
- 객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다. 객체의 공유 참조는 피할 수 없다.
이러한 부작용을 막고싶다면 기존의 address를 복사하여 새로운 copyAddress를 만들어 각각의 member엔티티에 사용하는 것입니다. 아래와 같습니다.
위와 같이 하면 member1의 city를 수정해도 부작용이 발생하지 않습니다.
하지만 개발자는 깜박하고 복사를 하지 않을 수 있습니다. 하여 컴파일러 단에서 막을 수 있는 방법이 있으면 좋을 것입니다.
불변 객체
• 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단
• 값 타입은 불변 객체(immutable object)로 설계해야함
• 불변 객체: 생성 시점 이후 절대 값을 변경할 수 없는 객체
• 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 됨
• 참고: Integer, String은 자바가 제공하는 대표적인 불변 객체
아래와 같이 setter를 private으로 하거나 아예 setter를 만들지 않으면 Address 값타입은 불변 객체가 됩니다.
이제 위의 코드에서 address를 수정하려하면 컴파일 에러가 발생합니다.
이제 address정보를 수정하기 위해서는 아래처럼 address를 새로 생성해서 수정해야합니다.
이렇게 불변객체로 만들어서 부작용을 방지할 수 있습니다.
값 타입의 비교
당연한 이야기지만 값타입을 비교하기 위해서는 equals메서드를 사용해야 합니다.
먼저 equals메서드를 override합니다.
실행결과:
• 동일성(identity) 비교: 인스턴스의 참조 값을 비교, == 사용
• 동등성(equivalence) 비교: 인스턴스의 값을 비교, equals()사용
• 값 타입은 a.equals(b)를 사용해서 동등성 비교를 해야 함
• 값 타입의 equals() 메소드를 적절하게 재정의(주로 모든 필드 사용)
● 컬렉션 값 타입
값타입을 컬렉션에 적용하여 사용할 수 있습니다.
• 값 타입을 하나 이상 저장할 때 사용
• @ElementCollection, @CollectionTable 사용
• 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
• 컬렉션을 저장하기 위한 별도의 테이블이 필요함
먼저 값타입 컬렉션을 저장해보겠습니다.
1. 저장
member엔티티에 다음과 같이 값타입 컬렉션을 추가합니다.
이렇게 하고 ddl을 날리면 아래와 같이 ADDRESS, FAVORITE_FOOD, MEMBER 테이블이 만들어집니다.
이제 값타입 컬렉션에 데이터를 저장해보겠습니다. 아래의 코드를 실행합니다.
결과:
잘 저장되는 것을 알 수 있습니다.
2. 조회
이제 조회를 해보겠습니다. 중요한 점은 값타입 컬렉션들에 지연로딩이 적용된다는 것입니다.
위와 같은 코드를 실행하면 지연로딩이 적용되어 member테이블의 member만을 select해옵니다.
하지만 favoriteFoods와 addressHistory의 값을 건드리면 역시 그에 대한 select쿼리가 나갑니다.
실행결과:
총 3번의 select문이 실행되고, 컬렉션의 값들이 잘 출력되는 것을 볼 수 있습니다.
3. 수정
기본적인 값(string)만 들어있는 favoriteFoods를 먼저 수정해보겠습니다.
위와 같이 값을 직접 넣어 remove하고, add해주면 됩니다.
하지만 addressHistory를 수정하는 경우에는 꽤 어려워집니다.
직접 Address와 같은 값을 가지는 객체를 new해서 remove를 하고, 새로운 객체를 add해주면 됩니다.
다만 아래와 같은 문제가 있습니다.
위에서 보시다시피 수정하려는 member_id를 가진 address정보를 전부 없애고 컬렉션에 있던 address데이터와 새롭게 추가된 address데이터를 전부 테이블에 insert합니다.
이와같은 현상은 아래의 값타입 컬렉션 제약사항과 관련이 있습니다.
값 타입 컬렉션의 제약사항
• 값 타입은 엔티티와 다르게 식별자 개념이 없다.
• 값은 변경하면 추적이 어렵다.
• 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
• 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 함: null 입력X, 중복 저장X
하여 위에서 Address와 같은 값을 가지는 객체를 new해서 remove를 하고, 새로운 객체를 add해주는 방법은 사용하면 안됩니다.
아래와 같은 대안을 사용해야 합니다.
값 타입 컬렉션 대안
• 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려
• 일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용
• 영속성 전이(Cascade) + 고아 객체 제거를 사용해서 값 타입 컬 렉션 처럼 사용 EX) AddressEntity
일대다 사용을 위해 수정된 코드는 아래와 같습니다.
Member
AddressEntity
Address(원래 있던 address는 그대로)
이제 아래와 같이 추가합니다.
정리하면 다음과 같습니다.
엔티티 타입의 특징 :
• 식별자O
• 생명주기관리
• 공유
값타입의특징 :
• 식별자X
• 생명 주기를 엔티티에 의존
• 공유하지 않는 것이 안전(복사해서 사용)
• 불변 객체로 만드는 것이 안전
값 타입은 정말 값 타입이라 판단될 때만 사용엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨
식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것 은 값 타입이 아닌 엔티티
'JPA > JPA원리' 카테고리의 다른 글
21. 실전예제 적용해보기 (0) | 2021.07.16 |
---|---|
20. 영속성 전이 : CASCADE, 고아객체 (0) | 2021.07.16 |
19. 지연로딩 (0) | 2021.07.16 |
18. 프록시 (0) | 2021.07.15 |
17. @MappedSuperclass (0) | 2021.07.14 |