코딩은 마라톤

[자바 ORM 표준 JPA 프로그래밍] 9장. 값 타입 본문

Backend/JPA

[자바 ORM 표준 JPA 프로그래밍] 9장. 값 타입

anxi 2024. 3. 17. 19:43

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

  • 새로운 값 타입을 직접 정의해서 사용할 수 있다. 이를 임베디드 타입이라 한다.
@Entity
public class Member {
	
    ...
    
    // 집 주소 표현
    private String city;
    private String street;
    private String zipcode;
    
    ...
}

 

  • 위 코드에서 회원 엔티티의 집 주소를 나타낼 때, 모든 정보를 풀어서 저장한다.
  • 이는 객체지향적이지 않으며 응집력만 떨어뜨린다.
  • 따라서 풀어쓰지 않고 [집주소]를 가지도록 임베디드 타입을 사용한다.
@Entity
public class Member {

	@Embedded Address homeAddress; // 집 주소
    ...
}
@Embeddable
public class Address {

	@Column(name = "city")
    private String city;
    private String street;
    private String zipcode;
}
  • city, street, zipcode를 합해서 Address(주소) 클래스를 만들었다.
  • 새로 정의한 값 타입들은 재사용할 수 있고 응집도 또한 아주 높다.

임베디드 타입 - 2가지 어노테이션

  • @Embeddable: 값 타입을 정의하는 곳에 표시
  • @Embedded: 값 타입을 사용하는 곳에 표시

또한 임베디드 타입은 기본 생성자가 필수다.

 

@AttributeOverride : 속성 재정의

@Entity
public class Member {

	@Embedded Address homeAddress;
    @Embedded Address companyAddress;
}
  • 회원 엔티티에 회사 주소를 하나 더 추가했다. 
    이때 테이블에 매핑하는 컬럼명이 중복되는 문제점이 발생한다. (homeAddress와 companyAddress의 @Embeddable 클래스인 Address 클래스의 컬럼명이 동일하다.)
  • 따라서 @AttributeOverrides를 사용해서 매핑정보를 재정의한다.
@Entity
public class Member {

	@Embedded Address homeAddress;
    
    @Embedded 
    @AttributeOverrides({
    	@AttributeOverride(name = "city", column=@Column(name="COMPANY_CITY")),
        @AttributeOverride(name = "street", column=@Column(name="COMPANY_STREET")),
        @AttributeOverride(name = "zipcode", column=@Column(name="COMPANY_ZIPCODE"))
    })
    Address companyAddress;
}

값 타입과 불변 객체

int a = 10; 
int b = a;
b = 4;
  • 위 코드는 자바의 기본 타입이다.
  • 기본 타입은 값을 대입하면 값을 복사해서 전달한다.
  • 따라서 b=4일 경우 a값은 변경되지 않는, 즉 a, b는 완전히 독립된 값을 가지고 있어 값 타입 복사의 부작용이 없다.
Address a = new Address("Old");
Address b = a;
b.setCity("New");
  • 문제는 위와 같은 객체 타입이다.
  • 객체에 값을 대입하면 항상 참조 값을 전달한다.
  • 따라서 위에서 b.setCity("New") 에서는 a.city의 값 또한 New로 변경된다.

위와 같은 문제를 해결하기 위해 "불변 객체"를 사용한다.

 

불변 객체

- 구현 방법

  • 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않는다.
@Embeddable
public class Address {
	
    private String city;
    
    protected Address() {} // JPA는 기본 생성자는 필수다.
    
    // 생성자로 초기 값을 설정한다.
    public Address(String city) { this.city = city; }
    
    // 접근자(Getter)는 노출한다.
    public String getCity() {
    	return city;
    }
    
    // 수정자(Setter)는 만들지 않는다.
}

값 타입 컬렉션

값 타입을 하나 이상 저장하려면 컬렉션에 보관하고 @ElementCollection, @CollectionTable 어노테이션을 사용한다.

@Entity
public class Member {
	
    @Id @GeneratedValue
    private Long id;
    
    @Embedded
    private Address address;
    
    @ElementCollection
    @CollectionTable(name = "FAVORITE_FOODS",
    	joinColumns = @JoinColumn(name = "MEMBER_ID"))
    @Column(name = "FOOD_NAME")
    private Set<String> favoriteFoods = new HashSet<>();
    
    @ElementCollection
    @CollectionTable(name = "ADDRESS",
    	joinColumns = @JoinColumn(name = "MEMBER_ID"))
    private List<Address> addressHistory = new ArrayList<>();
}

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

 

  • 값 타입 컬렉션은 영속성 전이(cascade) + 고아 객체 제거(orphan remove) 기능을 필수로 가진다.
  • 값 타입 컬렉션은 조회할 때 페치 전략을 선택할 수 있는데 FetchType.LAZY가 기본이다.