JPA 값 타입

JPA의 데이터 타입 분류

값 타입 분류

기본값 타입

임베디드 타입

새로운 값 타입을 직접 정의할 수 있다. JPA에서는 이를 임베디드 타입이라고 한다.
주로 기본값 타입을 모아서 만들기 때문에 복합 값 타입이라고도 한다.

@Entity
public class Member extends BaseEntity {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;
    
    private String city;
    private String street;
    private String zipCode;
    
    //..
}

city, street, zipcode를 묶어서 Address라는 값 타입을 만들어보자

@Embeddable //값 타입을 정의하는 곳에 표시한다.
public class Address {
    //임베디드 타입은 기본 생성자가 필수로 있어야한다.
    private String city;
    private String street;
    private String zipcode;
}
@Entity
public class Member extends BaseEntity {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;
    
    @Embedded //값 타입을 사용하는 곳에 표시한다.
    private Address address;
    
    //..
}

@Embeddable과 @Embedded 중 하나만 표시 해도 되지만
둘다 명시하는걸 권장

임베디드 타입의 값이 null이면 매핑한 컬럼 값도 모두 null이 된다.

임베디드 타입의 장점

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

@AttributeOverride: 속성 재정의

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

@Entity
public class Member extends BaseEntity {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    @Embedded
    private Address homeAddress;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name="city", column=@Column(name = "WORK_CITY")),
            @AttributeOverride(name="street", column=@Column(name = "WORK_STREET")),
            @AttributeOverride(name="zipcode", column=@Column(name = "WORK_ZIPCODE"))
     })
    private Address workAddress;
    
    //..
}

값 타입과 불변 객체

앞서 보았듯이 기본 값 타입은 공유가 불가능 하고 가능한 경우라도 불변이기 때문에 안전하다.
하지만 임베디드 타입은 값 타입이지만 객체이므로 공유가 가능하면서 변경이 가능하다.

Address address = new Address("city1", "street1", "zipcode1");
member1.setAddress(address);
member2.setAddress(address);

//member1의 city만 바꾸려고 했지만 member2의 city값도 바뀌어 버린다.
member1.getAddress().setCity("city2");

그렇기 때문에 값을 복사해서 사용해야 되지만 코드적으로 실수할 가능성이 있다.
그러므로 자바의 String처럼 불변 객체(immutable object)로 설계해야한다.

Address address = new Address("city1", "street1", "zipcode1");
member1.setAddress(address);
member2.setAddress(address);

//불변객체를 만드는 방법은 여러가지가 있으나 여기서는 setter를 없애고 생성자에서만 초기화 할 수 있도록 했다.
//member1.getAddress().setCity("city2");  
member1.setAddress(new Address("city2", address.getStree(), address.getZipcode()));

값 타입 비교

값 타입은 인스턴스가 달라도 그 안에 값이 같으면 같은 것으로 봐야한다.
== 의 경우 동일성 비교로서 인스턴스의 참조 값을 비교한다.
equals()의 경우 동등성 비교로서 인스턴스의 값을 비교한다.
그러므로 값 타입은 equals()를 사용해서 동등성 비교를 해야한다.

다음과 같이 equals() 메소드를 적절하게 재정의 해서 사용한다.

@Embeddable
@Getter
public class Address {

    private String city;
    private String street;
    private String zipcode;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return Objects.equals(city, address.city) &&
                Objects.equals(street, address.street) &&
                Objects.equals(zipcode, address.zipcode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(city, street, zipcode);
    }
}

값 타입 컬렉션

값 타입이 한 두개가 아니라 정말 여러개 일때는 값 타입 컬렉션이라는것을 사용할 수 있다.
데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없기 때문에 별도의 테이블이 필요하다.

@ElementCollection@CollectionTable을 사용해서 만들 수 있다.

@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD", joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME") //컬렉션이 단일 기본 값타입 하나 이므로 예외적으로 @Column사용가능
private Set<String> favoriteFood = new HashSet<>();

@ElementCollection
@CollectionTable(name = "ADDRESS", joinColumns = @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();

값 타입 컬렉션의 사용

값 타입 컬렉션은 영속성 전이 + 고아 객체 제거를 설정 한것 처럼 동작 한다.