프로그래밍/Spring

[ JPA ] 값 타입 리스트 동작 방식

Yanoo 2022. 1. 28. 01:06
728x90
반응형

 


import javax.persistence.Embeddable;
import java.util.Objects;

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

    public Address() {
    }

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

    public String getCity() {
        return city;
    }

    public String getStreet() {
        return street;
    }

    public String getZipcode() {
        return 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);
    }
}
import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
public class Member {

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

    @Column(name = "USERNAME")
    private String username;

    //주소
    @Embedded
    private Address homeAddress;

    @ElementCollection
    @CollectionTable(name = "FAVORITE_FOOD", 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<>();


    public Long getId() {
        return id;
    }

    // ... 이하 생략
}

이런 값 타입 리스트 구조를 가진다고 했을 때 어떻게 동작할까?

 Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("HomeCity", "street", "10000"));

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

member.getAddressHistory().add(new Address("old1", "street", "10000"));
member.getAddressHistory().add(new Address("old2", "street", "10000"));

em.persist(member);

em.flush();
em.clear();

System.out.println("============== START ==============");
Member findMember = em.find(Member.class, member.getId());

이렇게 만들어서 find해보면

FavoriteFoods와 addressHistory는 쿼리문을 날리지 않는다. 즉, 프록시로 가져오게 된다.

 

그리고 수정 시에 즉

homeCity에서 newCity로 바꾸려고 할 때,

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

이렇게 진행해야할 것 같지만 이렇게 진행하면 사이드 이펙드가 생긴다.

 

그래서

Address a = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", a.getStreet(), a.getZipcode()));

이렇게 진행하여야 한다.

 

치킨에서 한식으로 바꿀 때는

// 치킨 -> 한식
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");

지웠다가 다시 add 한다.

 

old1을 newCity1으로 바꿀 때도 까다로운데, hashCode와 equals가 오버라이드 되어있다고 가정하에

findMember.getAddressHistory().remove(new Address("old1", "street", "10000"));
findMember.getAddressHistory().add(new Address("newCity1", "street", "10000"));

이렇게 진행하면 된다. 그러나 결과를 보면

멤버에 해당하는 address를 다 지우고 지운 만큼의 address를 다시 insert하는 것을 확인할 수 있다.

 

이 이유는

  • 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.

라는 값 타입의 특징 때문이다.

 

그래서 현업에서는 명확하지 않으면 이 값 타입을 사용하는 것을 추천하지 않는다고 한다.


 

728x90
반응형