값 타입

김강현·2023년 3월 25일
1

ORM-JPA

목록 보기
7/9

JPA 의 데이터 타입 분류

엔티티 타입

  • @Entity 로 정의하는 객체
  • 데이터가 변해도 식별자로 지속해서 추적 가능

값 타입

  • int, integer, String 처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
  • 식별자가 없고 값만 있으므로 변경시 추적 불가
  • 생명주기를 엔티티에 의존

값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념!

값 타입 종류

기본값 타입

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

임베디드 타입

  • embedded type, 복합 값 타입
  • 새로운 값 타입을 직접 정의할 수 있음
  • 기본 값 타입을 모아놔서, 복합 값 타입이라고도 함
<Member> 회원은 이름, 시작일자, 종료일자, 도시명, 도로명, 우편번호를 가진다
Long id;
String name;
LocalDateTime startDtae;
LocalDateTime endDate;
String city;
String street;
String zipcode;
<Member> 회원은 이름, 근무 기간, 집 주소를 가진다
Long id;
String name;
Period workPeriod;
Address homeAddress;


jpa 에서는 @Embeddable (값 타입 정의하는 곳), @Embedded (값 타입 사용하는 곳) 에 표시를 한다.
( + 기본 생성자 필수 )

복합 값 타입(임베디드 타입)에 엔티티도 들어올 수 있다.
외래키로 불러오기만 하면 되는거임.
!! 같은 값 타입을 여러번 사용하고 싶을때

  • @AttributeOverride 라는 속성을 사용해 지정해줘야함 (Column 이름을)
@AttributeOverrides({
  @AttributeOverride(name="city", column=@Column(name = "xxx")),
  @AttributeOverride(name="street", column=@Column(name = "xxx")),
  @AttributeOverride(name="zipcode", column=@Column(name = "xxx")),
})

조심해야 할 것!!
임베디드 값 타입을 하나의 인스턴스로 여러개를 동시에 공유하면 안됨!!
같은 값을 주고 싶을 때는, clone 을 만들어서 넣어주어야함!
-> 개발자가 신경을 쓰는 부분... human mistake가 충분히 있을 수 있음....

==> 객체 타입의 한계.. 다른 방법 없을까?

불변객체 (immutable object)

  • 생성 시점 이후 절대 값을 변경할 수 없는 객체
  • setter 를 만들지 않기!!
  • Side Effect가 생길 가능성을 없애줌!
    => 가급적 임베디드 타입은 불변객체로 만들어 주는 것이 좋음!

값 타입 비교

  • 동일성 비교 : 인스턴스의 참조값을 비교 ( == )
  • 동등성 비교 : 인스턴스의 값을 비교 ( equals() ) <- 따로 만들어줘야함.

Address.java

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

위 경우엔 city, street, zipcode 직접 접근 한다.

이렇게 체크를 해주어서, getter 로 접근 할 수 있도록 하는 것이 좋음
(프록시 객체의 경우 에러가 날 수 있음)

equals 함수를 override 할때는 기본적으로 제공해주는 것을 사용하는 것이 좋음!!

컬렉션 값 타입

  • 값 타입을 하나 이상 저장할 때!
  • @ElementCollection, @CollectionTable 을 사용!
  • 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없음
  • 컬렉션을 저장하기 위해 별도의 테이블이 필요함!
  • 주 테이블에는 실질적인 Column이 없음!
  • 자동으로 영속성 전이, 고아객체 제거 기능이 있음
  • 기본적으로 컬렉션 값 타입은 지연로딩 임!

세팅

Member.java -> @ElementCollection, @CollectionTable

@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    @ElementCollection
    @CollectionTable(name = "FAVORITE_FOOD",
                     joinColumns = @JoinColumn(name = "MEMBER_ID"))
    @Column(name = "FOOD_NAME") // 안해도 되지만, 해당 테이블에서의 Column 명이 애매
    private Set<String> favoriteFoods = new HashSet<>();

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

Address.java

@Embeddable
public class Address {
    public Address() {
    }

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }

    private String city;
    private String street;
    private String zipcode;
}
Member member = new Member();
member.setName("changer");

member.getAddressHistory().add(new Address("city1", "street1", "1234"));
member.getAddressHistory().add(new Address("city2", "street2", "1234"));

member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("피자");
member.getFavoriteFoods().add("족발");

em.persist(member);


위와 같이 자동으로 Table 이 생성된다.

수정

Member findMember = em.find(Member.class, member.getId());

findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("삼계탕");

이렇게 하면 자동으로 table에 업데이트 된다.

addressHistory 의 경우에는

member.getAddressHistory().add(new Address("city1", "street1", "1234"));
...
...
Member findMember = em.find(Member.class, member.getId());

// findMember.getFavoriteFoods().remove("치킨");
// findMember.getFavoriteFoods().add("삼계탕");

findMember.getAddressHistory().remove(new Address("city1", "street1", "1234"));
findMember.getAddressHistory().add(new Address("city3", "street3", "1432"));

equals 함수가 구현이 되어있다면, remove 함수는 equals 가 true 인 항목을 없애준다.
add는 똑같이 하면 된다.

잘 돌아가는 것 같지만, 영속성 컨텍스트가 실제로는 우리 생각처럼 DB query 를 날리지 않음
(update 가 아닌, 통으로 delete 후 create 를 함!)

값 타입 컬렉션의 제약사항

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

이거 너무 제약사항이 많은걸? 다른 대안 없어??!!

일대다 고려

  • 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressEntityList = new ArrayList<>();

// @ElementCollection
// @CollectionTable(name = "ADDRESS",
//                  joinColumns = @JoinColumn(name = "MEMBER_ID"))
// private List<Address> addressHistory = new ArrayList<>();
member.getAddressEntityList().add(new AddressEntity("city1", "street1", "1234"));
member.getAddressEntityList().add(new AddressEntity("city2", "street2", "1234"));
...
...
findMember.getAddressEntityList().remove(new AddressEntity("city1", "street1", "1234"));
findMember.getAddressEntityList().add(new AddressEntity("city3", "street3", "1432"));

값 타입 컬렉션은 정말로 컬렉션 개수가 몇개 안될때! 막 쌓이는 것들이 아니고

profile
this too shall pass

0개의 댓글