개발 일기

[섹션 9] 값 타입 본문

Back-End/자바 ORM 표준 JPA 프로그래밍

[섹션 9] 값 타입

개발 일기장 주인 2024. 4. 9. 14:26

기본값 타입

엔티티 타입
@Entity로 정의하는 객체
데이터가 변해도 식별자로 지속해서 추적 가능하다.
ex) 회원 엔티티의 키, 나이 값이 변경 되도 식별자로 인식 가능

값 타입

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

값 타입 분류

  1. 기본값 타입
    - 자바 기본 타입(int, double) - primitive type
    - 래퍼 클래스(Integer, Long) 
    - String

    생명 주기를 엔티티에 의존, ex) 회원 삭제 시 이름, 나이 등 필드들 함께 삭제
    값 타입은 공유하면 X, ex) 회원 이름 변경 시 다른 회원의 이름도 함께 변경되면 안됨
  2. 임베디드 타입 
    ex) x,y 좌표 담을때 position, 주소 등

  3. 컬렉션 값 타입
참고: 자바의 기본 타입은 절대 공유 X
int, double 같은 기본 타입은 절대 공유 되지않고 항상 값만 복사된다.
예를 들어 int a = 1; int b = a, int a = 10;이라고 했을때 b의 값은 여전히 1이다.

그러나 Integer같은 래퍼 클래스나 String 같은 특수한 클래스는 공유 가능하다. 왜냐하면 이들은 메모리 주소 값을 참조하기 때문에 값이 복사되지 않고 메모리 주소 값이 공유되기 때문이다. 그러나 이때 자바 자체에서 값 변경을 막는다.

 

임베디드 타입(복합 값 타입)

JPA는 임베디드 타입이라 하고 새로운 값 타입을 직접 정의할 수 있고 주로 기본 값 타입을 모아서 만들기 때문에 복합 값 타입이라고도 한다. 이때 임베디드 타입은 int, String과 같은 값 타입이다. 즉, 추적 불가하다.

 

오른쪽 사진과 같이 회원 엔티티는 이름, 근무 시작일, 근무 종료일, 주소 도시, 주소 번지, 주소 우편번호를 가지는데 뭔가 묶어서 공통으로 처리하면 더 효과적으로 처리할 수 있을까 하는 생각이 든다.

그래서 근무 시작일과 근무 종료일을 묶어서 workPeriod를 만들고 도시, 번지, 우편번호를 묶어서 homeAddress 임베디드 타입(복합 값 타입)으로 만들어 처리하 할 수 있다.

 

임베디드 타입(복합 값 타입)

그래서 이제 이와 같이 처리를 하면 회원 엔티티는 이름, 근무 기간, 집 주소 이렇게 3가지로 모두 처리가 가능해 진다.

 

장점

  1. 재사용성
  2. 높은 응집도
  3. Period.isWork()처럼 해당 값 타입만 사용할 수 있는 의미 있는 메소드를 만들 수 있다.
  4. 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명주기를 의존한다.

 임베디드 타입과 테이블 메핑

임베디드 타입은 엔티티의 값일 뿐이다. 

임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.

객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가능하다.

 

잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많음

 

 

 

 

 

 

사용법

// 복합 값 타입을 사용하는 곳 
@Entity
public class Member {
    // ..
    @Embedded
    private Period workPeriod;
    
    @Embedded
    private Address homeAddress;;
    // ..
}


// 복합 값 타입을 정의하는 곳
@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;
    
    // 기본 생성자 필수
}

@Embeddable
public class Period {
    private LocalDateTime startDate;
    private LocalDateTime endDate;

    // 기본 생성자 필수
}

 

참고: Member 엔티티에서 Address라는 임베디드 타입을 사용할때 임베디드 타입의 필드로 값 타입도 가능한데 엔티티도 가능하다!

 

@AttributeOverride: 속성 재정의

이때 한 엔티티에서 같은 값 타입을 사용한다면? 컬럼명이 중복될 수 있다.

그렇기 때문에 @AttributeOverrides, @AttributeOverride를 사용해서 컬러 명 속성을 재정의

 

ex) 예를 들어 Address 임베디드 값 타입을 homeAddress로 쓸수 있고 workAddress로도 쓸수 있을 것이다.

그러면 이때 컬럼명이 중복되기 때문에 오른쪽 사진과 같이 해당 어노테이션들로 각 컬럼  명을 재정의 해줘야 한다.

 

 

 

 

참고: 임베디드 타입의 값이 null이면 매핑한 테이블에서 컬럼 값은 모두 null

 

값 타입과 불변 객체

값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념이다.
따라서 값 타입은 단순하고 안전하게 다 룰 수 있어야 한다.

값 타입 공유 참조

임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.

예를 들어 회원1과 회원2에게 같은 address 임베디드 타입 객체를 넣어주면 아래와 같은 부작용이 발생할 수 있다. member1.getAddress.setCity()로 회원1의 주소 도시를 변경해주더라도 같은 임베디드 타입 객체를 사용하던 회원2의 도시 값도 변경되는 문제가 발생할 수 있다. 이것을 의도했더라도 그때는 임베디드 값 타입이 아닌 엔티티를 사용해야한다.

 

그렇기 때문에 이문제를 해결하기 위해서는?

값 타입 복사를 통해 해결할 수 있다.

실제 복합 값 타입의 실제 인스턴스의 값을 공유하는 것은 위험하기 때문에 왼쪽 그림과 같이 값(인스턴스)을 복사해서 사용하여 하나의 다른 복합 값 타입 인스턴스를 만들어서 처리해야한다. 

항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용 을 피할 수 있다.

 

 

임베디드 타입은 객체 타입인데 객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다. 객체의 공유 참조는 피할 수 없다!

기본 타입은 단순히 값을 복사하지만 객체 타입은 참조를 전달(복사)한다.

 

불변 객체

  • 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단할 수 있다.
  • 값 타입은 불변 객체(immutable object)로 설계해야하고
  • 불변 객체: 생성 시점 이후 절대 값을 변경할 수 없는 개체
  • 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 된다.(아예 없에거나 private)  
  • 참고: Integer, String은 자바가 제공하는 대표적인 불변 객체이다.
  • 만약 바꾸고싶다면  각각의 컬럼 값을 변경할 수 없기 때문에 완전히 새로운 인스턴스를 만들어서 통으로 집어넣어야 한다.

값 타입의 비교

단순 값 타입은 인스턴스가 달라도 그 값이 같으면 true이다.

그러나 객체 타입은 그렇지 않다. 왜냐하면 전에도 언급했듯이 참조 값이 저장되기 때문이다.

 

동일성(identity) 비교

인스턴스의 참조 값을 비교, == 사용한다.

동등성(equivalence) 비교

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

 

값 타입 컬렉션 -> 보충 필요

값 타입 컬렉션은 값 타입을 하나 이상 저장할 때 사용한다.

@ElementCollection, @CollectionTable 사용하여 매핑
데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다. 그래서 컬렉션을 저장하기 위한 별도의 테이블이 필요하다.

 

제약사항

값 타입은 엔티티와 다르게 식별자 개념이 없다. 값은 변경하면 추적이 어렵다.

값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다. 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 함: null 입력X, 중복 저장X

 

대안

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