본 정리 내용은 "김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편" 을 듣고 정리한 내용이며, 중요한 내용이 판단된 부분은 강의자료의 사진자료를 사용하였습니다. 

 

기본값 타입

 

엔티티 타입

@Entity로 정의하는 객체이다. 데이터가 변해도 식별자로 지속해서 추적이 가능하다. 예를 들어 회원 엔티티의 키나 나이값을 변경해도 식별자로 인식 가능하다.

 

값 타입

int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체이다. 식별자가 없고 값만 있으므로 변경 시 추적이 불가하다. 예를 들어 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체된다.

 

 

 

값 타입의 분류

 

1. 기본값 타입

-자바 기본타입(int, double)
-래퍼 클래스(Integer, Long, String)

- 예) String name, int age
- 생명주기를 엔티티에 의존한다. 회원을 삭제하면 이름, 나이 필드도 함께 삭제 된다.
- 값 타입은 공유하면 안된다. 회원 이름 변경시 다른 회원의 이름도 함께 변경되면 안된다. 

 


2. 임베디드 타입(embedded type, 복합 값 타입)

- 새로운 값 타입을 직접 정의할 수 있다. 
- JPA는 임베디드 타입(embedded type)이라고 한다.
- 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 한다.
- int, String과 같은 값타입이다. 추적도 되지 않고 변경하면 다른값으로 대체된다.
- @Embeddable : 값 타입을 정의하는 곳에 표시
- @Embedded : 값 타입을 사용하는 곳에 표시
- 기본 생성자가 필수로 필요하다.

Member 클래스에 Period, Address 임베디드 타입 추가

 

Member 클래스에 Period, Address 라는 이름의 임베디드 타입을 추가해보자.

 

 

Address와 Peroid 클래스를 만들 때 @Embeddable 애노테이션을 붙여준다. (Getter/Setter는 생략)

 

 

Member 클래스에서 @Embedded 애노테이션을 사용하여 값 타입을 사용하면 된다. @Embedded 애노테이션은 생략이 가능하지만 명시해주는 것이 좋다.

 

 

임베디드 타입은 엔티티의 값일 뿐이다. 임베디드 타입을 사용하기 전과 후의 매핑하는 테이블은 같다. 하지만 Member 엔티티의 확장성이 생기면서 객체지향적인 코드가 된다. 예를 들어, Period클래스에 현재시간과 비교하여 기간을 산출하거나 총 기간을 계산하는 등 기간과 관련된 의미있는 메서드를 만들어서 활용할 수도 있게 된다.

임베디드 타입을 사용하면 객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가능해진다. 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많다.

 


<장점>
- 재사용이 가능하다.
- 높은 응집도를 가진다.
- 해당 값 타입만 사용하는 의미 있는 메소드를 만들 수 있다.
- 임베디드 타입을 포함한 모든 값 타입은 값 타입을 소유한 엔티티에 생명주기를 의존한다.


- @AttributeOverride : 속성 재정의
한 엔티티에서 같은 값 타입을 사용하면? 컬럼명이 중복된다. @AttributeOverride를 사용해서 컬러명 속성을 재정의하면 된다. 

@AttributeOverride의 사용방법

 

 

3. 컬렉션 값 타입(collection value type)

 

 



값 타입과 불변 객체

임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다. 부작용(side effect)이 발생할 수 있다.

 

의도는 member1의 city를 고치고 싶었겠지만, 임베디드 타입은 값 타입이기 때문에 member1의 city와 member2의 city가 모두 "daejeon"으로 바뀌게 된다.

 

 



값 타입의 실제 인스터스인 값을 공유하는 것은 위험하다. 대신 값(인스턴스)를 복사해서 사용해야 한다.

이렇게 사용하거나 혹은 임베디드 타입의 장점을 이용해서 Address클래스에 인스턴스를 복사하는 메서드를 만들어서 사용할 수도 있겠다.



이렇게 항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다. 그런데 문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입이다.
객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다. 객체의 공유 참조를 피할 수 없다. (타입만 맞으면 다 대입이 된다)

해결방법은?
객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단할 수 있다. 값 타입은 불변객체(immutable object)로 설계해야 한다. 
생성자로만 값을 설정하고 수정자(setter)를 만들지 않으면 된다. 참고로 Integer, String 은 자바가 제공하는 대표적인 불변객체이다.

 

 

 

 


값 타입의 비교

동일성(identity)비교 : 인스턴스의 참조 값을 비교, == 사용
동등성(equivalence)비교 : 인스턴스의 값을 비교, equals()사용


값 타입은 a.equals(b)를 사용해서 동등성을 비교해야 한다. 
값 타입의 equals()메소드를 적절하게 재정의 해야한다.(주로 모든 필드 사용)

 

 

 



값 타입 컬렉션 

값 타입을 하나 이상 저장할 때 사용한다. 
@ElementCollection, @CollectionTable 사용해서 매핑하면 된다. 
데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다. 컬렉션을 저장하기 위한 별도의 테이블이 필요하다.

값 타입 컬렉션을 추가한 테이블 구조

 

이와 같이 @ElementCollection과 @CollectionTable 애노테이션을 써서 매핑해주면 된다. @CollectionTable의 속성으로 joinColumns = @JoinColumn(name="MEMBER_ID" 를 지정해줌으로써 MEMBER_ID를 FK로 가지는 컬렉션 테이블을 생성해주게 된다.

 

 

 


값 타입의 저장예제

 

 

컬렉션 값타입은 다른 테이블임에도 불구하고 라이프 사이클이 같이 돌아 갔다. 왜냐면 이것은 값타입 이기 때문이다. 그렇기 때문에 별도로 persist를 하거나 update를 하거나 그럴 필요가 없다
마치 값 타입 컬렉션은 영속성 전이(cascade = all)와 고아 객체 제거기능(orphanRemoval = true) 속성을 모두 써준것과 같이 동작한다.

 

 

 

 

 


값 타입의 조회예제

 


Member를 조회하면 Member만 Select한다. 그 말은 즉슨, 값타입 컬렉션도 지연로딩 전략을 default로 사용한다는 의미이다. 실제로 사용하는 시점이 와야 그제서야 컬렉션 값 타입의 데이터를 불러와서 가져올 것이다.
(참고로 Embedded타입은 Member에 소속된 값 타입이기 때문에 조회 시 같이 불러와진다.)

 

 



값 타입의 수정예제

 

 

컬렉션 값 타입의 예를 보기전에 임베디드 값 타입의 수정을 잠깐 살펴보자.

잘못된 예


컬렉션 값 타입도 결국 값 타입 이기 때문에 setter를 이용해서 수정하면 안된다! side effect 발생
인스턴스 자체를 갈아끼워야 한다.

올바른 예

이렇게!

 

 

 

 

그러면 값 타입 컬렉션의 수정은 어떻게 하면 될까?

 

 

값 타입 컬렉션일 경우, 방법이 따로 없다. remove()를 이용해서 해당 컬렉션의 데이터를 삭제 한 후, 다시 새로운 데이터를 add해서 넣어주어야 한다.

이것만 봐도 효율이 떨어지는 것 같기는 한데 더 큰 문제는 한개가 더 있다.

 

분명 한개의 데이터만 삭제하고 추가로 한개의 데이터만을 add 했는데 SQL은 delete한개와 insert 2개가 나간다. 값 타입 컬렉션에 변경사항이 발생하면 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.

 

 


값 타입 컬렉션의 제약사항

- 값 타입은 엔티티와 다르게 식별자 개념이 없다.
- 값은 변경하면 추적이 어렵다.
- 값 타입 컬렉션에 변경사항이 발생하면 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 한다. (null 입력x, 중복저장x)

모든 컬럼을 묶어서 PK값으로 사용하는 테이블을 생성했었음.

 

 

 

 

값 타입 컬렉션 대안

- 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려해야 한다.(값 타입에서 엔티티로 승격)
- 일대다 관계를 위한 엔티티를 만들고, 여기에 값 타입을 사용하는 방법으로 해야한다.
- 영속성 전이(Cascade) + 고아 객체 제거를 사용해서 값 타입 컬렉션처럼 사용한다.

 

 

이처럼 AddressEntity라는 엔티티를 한개 만들어서 Address값 타입을 한번 래핑해주는 것이다.

 

 

 

List<Address>가 아닌 방금 만든 List<AddressEntity>를 사용하고 일대다 매핑을 해준다! 그리고 '영속성 전이(Cascade) + 고아 객체 제거'를 사용하여 값 타입 컬렉션처럼 이용한다.

 

 


- 값 타입 걸렉션은 만약 멀티 체크박스에서 내가 좋아하는 메뉴 여러개 선택가능하게 하는 기능이 있다. 그렇게 값이 단순하고 추적할 필요도 없고, 값이 바뀌어도 update할 필요가 없을 때 사용하는 것이다!
예를 들어 주소 이력 과 같은 정보는 무조건 엔티티로 사용해야 한다.

- 값 타입은 정말 값 타입이라고 판단 될때만 사용해야 한다. 엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안된다. 식별자가 필요하고, 지속해서 값을 추적 및 변경해야 한다면 그것은 값 타입이 아니라 엔티티로 만들어야 한다.

 


정리

엔티티 타입의 특징

- 식별자가 있음
- 생명 주기가 관리 됨
- 공유를 할 수 있음


값 타입의 특징

- 식별자가 없음
- 생명주기를 관리하지 못하여 엔티티에 의존함
- 공유하지 않는 것이 안전
- 만약 공유해야 한다면 불변객체로 만드는 것이 안전

+ Recent posts