값타입과 불변객체
값타입은 안전하고 단순하게 다룰 수 있어야 한다.
하지만, 값타입을 공유참조하며 문제가 생긴다.
임베디드 타입을 여러 엔티티에서 공유할 경우 부작용이 발생할 수 있다.
그렇기에 불변객체로 설계하는 방식을 사용한다.
불변객체란, 객체 타입을 수정할 수 없게 만들어 부작용을 원천 차단하는 방식이다.
Constructor이후에 Setter를 통해 객체를 수정할 수 없게 사용하는 방식이다.
우아한 테크코스에서 Setter를 사용하지 않고 객체를 만들어 쓰라고 했던 기억이 있다.
이래서 그런건가 싶기도 하다.
값타입 컬렉션
값타입 컬렉션을 DB에 구현하는게 쉽지 않다.
컬렉션 자체를 테이블에 담는게 관계형 데이터베이스에 바로 넣기보단, 다른 테이블에 담아주어야 한다.
컬렉션을 구성하는 데이터로 테이블을 따로 만들어야 한다.
예를 들어, 멤버에 컬렉션으로 데이터를 담으려면,
Member_id, 컬렉션의 값의 구조로 테이블이 생기게 된다.
이에 대한 구현 방식은,
이런방식으로 이루어지게 되는데,
@ElementCollection
어노테이션과
@CollectionTable를 사용한다.
이를 통해, 해당 컬렉션을 이루는 테이블의 이름과, MEMBER_ID를 외래키이자 기본키로 사용하는 것을 설정할 수 있다.
값타입 컬렉션은
값 타입을 하나 이상 저장할 떄 사용한다(컬렉션으로 만들어 저장해야 할 때)
@ElementCollection
@CollectionTable
데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
컬렉션을 저장하기 위한 별도의 테이블을 만들어 저장시킨다.
이를 확인하는 방법은,
이렇게 테이블을 불러오면?
이렇게 테이블이 분리되어 나온다.
그런데, 이게 좋은 점은 무엇일까?
try{
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("city", "address", "zipcode"));
System.out.println(member.getUsername());
System.out.println(member.getHomeAddress().getCity());
System.out.println(member.getHomeAddress().getStreet());
System.out.println(member.getHomeAddress().getZipcode());
System.out.println("-------------------------------");
member.getFavoriteFood().add("pasta");
member.getFavoriteFood().add("pizza");
member.getFavoriteFood().add("chicken");
for(String food : member.getFavoriteFood()){
System.out.println(food);
}
System.out.println("-------------------------------");
member.getAddressHistory().add(new Address("city2", "address2", "zipcode2"));
member.getAddressHistory().add(new Address("city3", "address4", "zipcode5"));
for(Address address: member.getAddressHistory()){
System.out.println(address.getCity());
System.out.println(address.getStreet());
System.out.println(address.getZipcode());
}
System.out.println("-------------------------------");
em.persist(member);
tx.commit();
이런 방식으로 코드를 작성했을 때,
이를 member하나를 영속화 시켜도 해당 데이터를 모두 각 테이블에 넣어준다는 것이다.
이 상태에서 Member를 데이터베이스에서 불러와 조회해보면, Member만 나오고, favoriteFood, addressHistory는 같이 조회되지 않는다.
지연로딩임을 알 수 있다!!!!
(왜 먼저 배웠겠어..)
값타입을 저장하고, 조회까지는 해보았다.
수정할 때에 조금 알아보아야 할 부분이 있다.
Member의 homeAddress를 기준으로 정리를 해보자면,
Address의 필드를 수정하는 것이 아닌, 새 Address를 넣어 바꾸어 주어야 한다.
왜냐하면, 값타입은 추적이 불가능 하기 때문에, 이를 가져와 한 부분만 바꾸어주는 것이 아닌,
새로운 값 타입으로 갈아끼워 주는 것이 더 나은 방법이다.
이런식으로 Address에 대한 값을 바꾸어 주어야 한다.
그렇다면, 값타입 컬렉션은 어떻게 수정해야 할까?
favoriteFood에 들어간 값중 하나를 바꾼다는 경우에 수정을 해본다면,
이런 방식으로 기존에 있던 값타입을 지우고, 새로운 값타입을 넣어주면 된다.
이 때 문제가 있다.
음식의 경우에는,
하나를 지우고 하나가 추가되었다.
근데 왜 주소는 2개가 insert되는 걸까?
이게 왜 이러는지 이해를 해보려고 한다.
값타입은 식별자가 존재하지 않고,
추적이 어렵다.
값타입 컬렉션에 변경 사항이 존재한다면, 주인엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
값타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성해야 한다. 즉, null과 중복이 허용되지 않는다(그게 기본키니까)
사실, Address를 이렇게 쓰기보단, 그냥 테이블을 따로 만들어서 사용하는 것이 더 나을 수 있다.
아니면,
이런식으로 넣을 수 있긴 하다.
근데, 그거보단 Entity를 따로 만드는게 낫지 않을까?
일대 다 관계로 테이블을 만들어 저장하는 방식이 더 낫다.
여기에, 영속성 전이와 고아 객체 제거를 사용하며 엔티티로 사용하는게,
값타입 컬렉션에서 장점이던 영속성 전이 + 고아 객체 제거의 기능을 사용하면서 모두 제거하고 다시 저장하는 값타입의 단점을 제거시키는 방법이다.
이런식으로 AddressHistory를 바꾸어 주는것이 더 안전하다.
그러면 언제쓸까?
정말 단순한 값들을 만들 때, 예를 들면 사용자에게 선택사항을 줄 때, 남자/여자 중에 골라라에서 남자와 여자를 저장하는 등의 단순한 구조에서만 사용하는 것이 좋다.