본문 바로가기

Backend/Spring

값 타입

jpa의 데이터 타입은 다음과 같이 분류할 수 있다.

  • 엔티티 타입
  • 값 타입
    • 기본 값 타입
      • 자바 기본 타입 (int, double)
      • 래퍼 클래스 (Integer, Long)
      • String
    • 임베디드 타입
    • 컬렉션 값 타입

 
엔티티 타입의 경우, @Entity로 정의하는 객체이다.
데이터가 변하더라도 식별자로 지속해서 추적이 가능하다는 특징을 가지고 있다.
 
값 타입은 int, Integer, String처럼 값으로 사용하는 자바 기본 타입이나 객체를 의미한다.
엔티티 타입과 다르게 식별자가 없고 값만 있어서 변경되면 추적이 불가하다.
 

기본 값 타입

자바 기본 타입은 항상 값을 복사하는 반면, Integer 같은 래퍼 클래스나 String 같은 특수한 클래스는 공유 가능한 객체이지만 변경은 불가능하다.
 

임베디드 타입

임베디드 타입의 경우 새로운 값 타입을 직접 정의할 수 있다.
주로 기본 값 타입들을 모아서 만든다.
 
다음 회원 엔티티가 있다고 하자.

 
위 엔티티는 임베디드 타입을 활용하면 보다 깔끔하게 관리할 수 있다.
startDate, endDate 필드를 Period 임베디드 타입에서 관리하고, city, street, zipcode를 Address 임베디드 타입에서 관리하도록 수정하였다.

 
임베디드 타입을 사용하면 재사용도 가능하고, 높은 응집도를 가지게 된다.
그리고 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 동일하긴 하지만, 객체를 조금 더 객체 지향스럽고, 깔끔하게 관리할 수 있는 것이다.
 
@Embeddable 어노테이션은 값 타입을 정의하는 곳에, @Embedded는 값 타입을 사용하는 곳에 표시하면 임베디드 타입을 사용할 수 있다.
 
그리고, 한 엔티티에서 같은 값 타입을 사용할 때는 @AttributeOverride라는 어노테이션을 사용해서 속성을 재정의해주어야 한다.
이는 컬럼 명이 중복되기 때문이다.
 
다음과 같이 나타내면 된다.

@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("city", "street", "10000");

Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);

Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());

Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(copyAddress);
em.persist(member2);

member.getHomeAddress().setCity("newCity");

tx.commit();

임베디드 타입처럼 직접 정의한 값 타입은 객체 타입이기 때문에, 참조 값을 직접 대입하는 것을 막을 방법이 없다.
그래서, 객체 타입을 수정할 수 없게 만들면 된다.
값 타입은 불변 객체로 설계해서, 생성자로만 값을 설정하고 수정자를 만들지 않거나 private으로 선언하면 된다는 것이다.
 

값 타입의 비교

- 동일성 비교: 인스턴스의 참조 값 비교, == 사용
- 동등성 비교: 인스턴스의 값을 비교, equals() 사용
 
값 타입은 equals()를 사용하는 동등성 비교를 해야 한다.
또한, 이 메서드를 적절하게 재정의해서 사용해야 한다.
equals() 재정의 시 그에 맞는 hashCode도 같이 작성해주어야 한다.

@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);
}

 

값 타입 컬렉션

값 타입 컬렉션은 값 타입을 컬렉션에 담아서 쓰는 것이다.
하지만, 관계형 DB에 컬렉션을 테이블에 담을 수 없다.
그래서, 일대다 개념이라 별도의 테이블로 구현해야 한다.
 
추가적으로, 값 타입 컬렉션은 다음과 같은 특징을 가지고 있다.
- 값 타입 컬렉션은 영속성 전이 + 고아 객체 제거 기능을 필수로 가진다.
- 엔티티와 다르게 식별자 개념이 없다.
- 값을 변경하면 추적이 어렵다.
- 값 타입 컬렉션에 변경 사항이 발생하면 주인 엔티티와 연관된 모든 데이터를 삭제하고 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 한다. => null 입력 X, 중복 저장 X
 
그래서 결론은 값 타입 컬렉션보다는 일대다 관계를 이용하는 것이 좋다.
 

실전 예제

 
이제 주소 부분을 Address로 만들고 적용해보자.

package jpabook.jpashop.domain;

import javax.persistence.Embeddable;

@Embeddable
public class Address {
	private String city;
	private String street;
	private String zipcode;

	public String getCity() {
		return city;
	}

	public String getStreet() {
		return street;
	}

	public String getZipcode() {
		return zipcode;
	}

	private void setCity(String city) {
		this.city = city;
	}

	private void setStreet(String street) {
		this.street = street;
	}

	private void setZipcode(String zipcode) {
		this.zipcode = 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(getCity(), address.getCity()) && 
						Objects.equals(getStreet(), address.getStreet()) &&
						Objects.equals(getZipcode(), address.getZipcode());
	}

	@Override
	public int hashCode() {
		return Objects.hash(getCity(), getStreet(), getZipcode());
	}
}

 

'Backend > Spring' 카테고리의 다른 글

프록시와 연관 관계 관리  (0) 2023.08.16
고급 매핑  (0) 2023.08.16
다양한 연관 관계 매핑  (0) 2023.08.16
엔티티 매핑과 연관 관계 매핑  (3) 2023.08.09
영속성 컨텍스트  (0) 2023.08.09