자바 ORM 표준 JPA 프로그래밍 - 기본편 - 섹션 9. 값 타입
CS/김영한 스프링 강의

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 섹션 9. 값 타입

기본값이랑 임베디드 값 알아볼건데, jpa하며 엔티티가 나오고 엔티티를 엔티티 타입이라고 설정해놓아서 순수 자바 객체 값들을 일반값이라고 정의한거다.

자바에서 순수 int, string같은 타입은 값에 클래스 주소값을 넣는게 아니라 막 0x00001010 이런걸 넣어서 실제 값을 저장한다는걸 알고있을거다.

근데 이제 Integer는 클래스라 주소값 &형식으로 저장할텐데 다른 변수도 이 주소값을 공유하도록 정의하면 이 클래스 안에있는 값이 바뀔경우 의도하지 않았는데 바뀌는 사이드 이펙트가 될거다. 하지만 이런 경우는 자바가 Integer같은 클래스를 만들 때 ㅅset으로 설정 자체가 불가능하게 막아놓았기 때문에 괜찮다.

set 자체가 없어서 못바꾼다

즉 순수 자바 타입들을 사용하면 사이드 이펙트가 생길 일이 없다.

이걸 알아야 임베디드 타입을 이해할 수 있다고 함.

 

 

자바 객체 적으로는 하나로 묶어서 하고 싶을 때가 있다. 하지만 jpa는 기본 자바 타입들만 알아들어서 ORM할 때 알맞게 타입을 맞출텐데, 내가 막 만든 클래스는 타입이 있을리가 없으니 에러가 뜬다. 이걸 @Embeddable이나 @Embedded를 사용하여 알려주는 것. 테이블 안에는 똑같은 값이 들어가지만, 자바 객체적으로 분리했기 때문에 유지보수하기 더 좋고 특히 안의 함수들도 따로 만들 수 있어 더 좋다.

 

 

 

@Embeded 안의 속성 값들을 그대로 가지고 와서 컬럼값으로 사용하기 때문에 똑같은게 있을 경우 중복 에러가 뜬다. 이런 경우 따로 이름을 설정해주면 된다.

 

 

하지만 내가 객체를 직접 만들었기 때문에 문제가 발생한다. 자바는 클래스의 경우 참조값(주소값)으로 하기 때문에 =를 써도 같은 클래스를 바라보아서 저쪽에서 바뀌었는데 이쪽도 바뀌는 사이드 이펙트가 일어난다.

 

그래서 같은 값을 만들고 싶으면 안의 속성까지 똑같이 배껴서 새로 객체를 만들어서 그걸 넣는다. 즉 deepcopy하라고.

 

근본적인 해결책은 Integer도 클래스지만 값 변경 set이 불가능했던 것 처럼 나도 @Embeddable 클래스를 만들 때 변경 불가능하게 만들어 놓을 것. 방법은 set 함수 자체를 안만들든 private로 하든.. 그러면 누가 변경하려면 일일히 복사해서 다시 만들어서 할 것이기 때문에 문제 자체를 방지할 수 있다. 사실 가장 문제였던건 실수로 그냥 set으로 막 바꿔도 이 버그 자체를 에러로써 아예 못 잡는 것이었는데 이런걸 미연에 방지할 수 있다.

 

 

같은건지 비교하는거

equals와 hashCode를 override 하면 된다.

 

equals와 hashCode를 override 하지 않으면 얘도 객체 주소값을 비교해서 false가 뜨지만 클래스 안에 있는 각 속성값들을 비교하도록 override해서 바꾸면 된다.

 

 

값 타입 컬렉션, 즉 위에서 본 기본 타입이든 만든 타입이든 이걸 List나 Set 같은 컬렉션에 넣었을 때를 보자.

컬렉션으로 타입을 저장하면 전에 했던 엔티티를 @OneToMany로 컬렉션을 저장했던 것 처럼 타입들도 테이블을 나눠 저장한다. 다만 전에 했던 cascade와 orphanRemoval = true로 했던것처럼 라이프 사이클은 동일하지만 어차피 자신의 id값을 넣어서 where로 불러오는 형식이니 독점적으로 소유할 필요는 없다.

 

@Embeded네 할 필요 없이 @ElementCollection 하면 된다. 자신의 키 컬럼에 접근할 수 있는 걸 명시해둔다. 즉 외래키 컬럼 정해준다고.

 

 

 

 

 

@ElementCollection은 기본 fetch_type이 LAZY라서 따로 설정 안해도 지연로딩이다.

 

 

새 객체를 넣으며 추가한다. sql문으로 update는 잘 나가고 table 상에는 어차피 한 쪽에 다 있는걸로 설정해놨으니 그렇게 된다.

 

문제는 삭제할 때. 여기서 값 타입 컬렉션 쓰지 말고 엔티티로 승격시켜서 쓰라고 얘기할건데 일단 삭제 및 다시 다른걸 추가할 때 어떻게 되는지 보자.

 

이렇게 코드와 결과만 보면 의도한 대로 잘 되어있는걸 볼 수 있다. 어떤걸 삭제하는건지 찾는것도 equals를 일일히 해서 찾는거기 때문에 만약 앞에서 equals를 정의 안해놨다면 여기서 큰일났을거다.

하지만 문제는 sql문이 어떻게 날라가는지다.

delete where로 해서 통째로 다 삭제하고 기존에 있던 걸 add하여 삭제하려던 것을 제외한 원 상태로 돌려놓은 뒤에 내가 추가하려던 것을 추가한다.

왜 이렇게 되느냐?? 그건 값 타입 컬렉션은 엔티티가 아니라 추적이 불가능하기 때문. 그래서 뭔가를 수정하려고 해도 추적하면서 가지고 있는게 없으니 모두 다 지우고 새로 하고 하는 방법밖에 없는것이다. 막 order 속성 어쩌구 넣으면서 어떻게든 할 수는 있지만.. 이 정도면 설계가 잘못된 것이기에 이 값 타입 컬렉션 대신 전에 배운 @OneToMany를 이용하여 엔티티를 새로 만든다. 이를 승격시키는 거라고 함.

 

그래서 엔티티를 만들어서 해보자. id 값을 가지고 엔티티이기 때문에 추적이 되서 위 같은 문제가 해결된다.

id가 새로 생겨 승격된다는 표현을 쓴 것.

모두 다 지우고 다시 추가하는 대신 update가 되며 깔쌈하게 된다.

 

그럼 결론은 값 타입 컬렉션은 진짜 진짜 진짜 간단할 때만 쓰고 평소엔 엔티티로 @OneToMany로 바꿔서 사용하자.

 

 

실전 예제에도 적용해보자.

@Embeddable
public class Address {

    @Column(length = 10)
    private String city;
    @Column(length = 20)
    private String street;
    @Column(length = 5)
    private String zipcode;

    public String fullAddress() {
        return getCity() + " " + getStreet() + " " + getZipcode();
    }

    public String getCity() {
        return city;
    }

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

    public String getStreet() {
        return street;
    }

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

    public String getZipcode() {
        return zipcode;
    }

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

그럼 일일히 속성 넣어주었던 것처럼 db에도 잘 저장됨을 볼 수 있다.

또 저렇게 클래스로 만들어 묶으면 좋은점이 공통적인 제약 사항들을 쉽게 적용할 수 있다는 것, 비즈니스 적인 함수들도 적용할 수 있다.