[JPA] 값 타입

Lee Seung Jae·2021년 7월 16일
0

JPA의 데이터 타입을 크게 분류하면 엔티티 타입과 값 타입으로 나눈다.
엔티티 타입은 @Entity로 정의하는 객체이고, 값 타입은 int, Integer, String 처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체를 말한다.
값 타입은 3가지로 나눌 수 있다.

  • 기본 값 타입
    • 자바 기본타입
    • 래퍼(Wrapper) 클래스
    • String
  • 임베디드 타입(복합 값)
  • 컬렉션 값 타입

기본 값 타입은 말그대로 자바가 제공하는 기본 데이터 타입이고, 임베디드 타입은 JPA에서 사용자가 직접 정의한 값이다. 컬렉션 타입은 하나 이상의 값 타입을 저장할 때 사용한다.

기본은 다뤘으므로 생략하도록 하겠다.

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

직접 정의한 임베디드 타입도 int, String 처럼 값 타입이다.

공통적으로 쓰는 어떤것(ex - 시간, 주소)들을 엔티티 클래스 마다 그대로 가지고 있으면 객체지향적이지 않으며 응집력이 떨어진다. 이런 공통 타입이 생기면 더 명확해진다.

임베디드 타입을 사용하려면 2가지 어노테이션이 필요하다.
둘 중 하나는 생략해도 된다.

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

임베디드 타입은 엔티티의 값일 뿐이다. 따라서 값이 속한 엔티티의 테이블에 매핑한다.
이 임베디드 타입 덕분에 객체와 테이블을 아주 세밀하게 매핑하는것이 가능하기 때문에 잘 설계된 애플리케이션은 매핑한 테이블 수보다 클래스의 수가 더 많다.

Mybatis로 개발을 한다면 테이블, 객체 1:1매핑을 한다. 그렇기에 객체지향으로 개발하려고 해도 이미 SQL에 너무나 의존적인 개발을 진행했기에 여러 클래스를 매핑하는 작업이 수월하지 않았다.

ORM인 JPA를 사용하면 귀찮은 반복 작업은 JPA에게 할당하고 모델을 설계하는데 집중할 수 있다.

이 기능으로 연관된 테이블은 모조리 @Embedded로 묶어 사용하는 그림이 그려진다❗️

@AttributeOverride : 속성 재정의

만약 주소가 집주소 그리고 회사 주소가 있다고 가정할때 클래스는 똑같은데 컬럼 값을 다르게 줘야한다면 속성을 재정의해서도 값을 줄수가 있다.

@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
public class Address {
    @Column(name = "city")
    private String city;

    private String street;

    private String zipcode;
}

@Embedded
private 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")),
})
private Address companyAddress;
        

이렇게해서 재정의를 해주면 companyAddress에는 override한 @Column 들이 매핑되게 된다.
그래서 SQL 쿼리문은 아래와 같이 출력된다.

여기서 column이 소문자로 나온 이유는 내가 JPA Buddy로 column설정을 무조건 언더바에 lowerCase로 나오게해서 그렇다.

설정이 없다면 대문자로 나오게 될 것이다.

이런식으로 공통적으로 쓰는것은 저번 강의에서 봤던 @MappedSuperClass와 같이 사용한다면 시너지가 극대화 될 것이라고 생각한다❗️

임베디드 타입과 null

임베디드 타입이 null이면 매핑한 컬럼 값은 모두 null이 된다.

member.setAddress(null); //null
em.persist(member);

멤버 테이블의 주소와 관련된 값은 모두 null이 된다.

값 타입과 불변 객체

값 타입은 단순하고 안전하게 다룰 수 있어야 한다.

값 타입 공유 참조

임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.

member1.setHomeAddress(new Address("OldCity"));
Address address = member1.getHomeAddress();

address.setCity("NewCity"); //멤버1의 address 공유
member2.setHomeAddress(address);

이렇게 update를 하게되면 멤버2만 NewCity로 변경이 되는 것이 아니라 멤버1의 주소도 NewCity로 변경된다. 이것은 영속성 컨텍스트가 멤버1, 2 둘 다 city 속성값이 변경된 것으로 생각하기 때문에 둘다 update 쿼리를 날리게 된다.

이런식으로 예상치 못한 곳에서 문제가 발생하는 것은 부작용이라고 한다. 이 부작용을 막기 위해선 값을 복사해서 사용하면 된다.

값 타입 복사

값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다. 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본타입이 아니라 객체 타입이다.
자바 객체는 CallByReference 이기 때문에 참조값을 전달한다.
clone()이 아니라 예를 들어

Address a = new Address("주소1");
Address b = a;
b.setCity("주소테스트");

이렇게 b Address에 a가 참조하는 인스턴스의 참조값 자체를 b에 넘기면 둘은 같은 인스턴스를 공유참조 한다. 이렇게되면 a의 city값도 변하게 되는 것이다.

인스턴스를 복사해서 대입하면 공유 참조를 피할 수 있는데 복사하지 않고 원본 참조 값을 직접 넘기는 실수를 완전하게 막을 수는 없다. 그래서 객체의 공유 참조는 피할 수 없다.

책에서 해결책은 setter메소드를 모두 제거하면 된다고한다. 제거하면 부작용의 발생을 막을 수 있다.

불변 객체

객체를 불변하게 만들면 값을 수정할 수 없다. 그렇기에 부작용 원천 차단이 가능하다.
따라서 값 타입은 될 수 있으면 불변 객체로 설계해야 한다.
불변 객체의 값은 조회할 수 있지만 수정할 수 없다. 이 불변 객체도 객체기에 참조값 공유를 피할 수는 없지만 수정이 불가능하므로 부작용이 발생할 우려는 없다.

이런데에서 깨달은 것이 바로 생성자에서 값을 할당하는 것이다.

@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Address {
    private String city;
    
    public Address(String city) {
        this.city = city;
    }
}

멤버1의 주소값을 조회하여 새로운 주소 생성

Address address = member1.getHomeAddress();
Address newAddress = new Address(address.getCity());
member2.setHomeAddress(newAddress);

이렇게 해서 불변이라는 작은 제약조건으로 부작용이라는 에러를 막을 수 있다.

값 타입의 비교

이것은 너무나 잘 알고들 있을거라고 생각한다.

  • 동일성 비교 : 인스턴스의 참조 값을 비교(주소 값을 비교하는것)
    • == 사용
  • 동등성 비교 : 인스턴스의 단순 값을 비교
    • equals() 사용

요약

엔티티 타입과 값 타입의 특징은 다음과 같다.

엔티티 타입의 특징

  • 식별자(@Id) 가 있다.
    • 엔티티 타입은 식별자가 있고 식별자로 구별할 수 있다.
  • 생명 주기가 있다.
    • 생성 → 영속화 → 소멸 의 주기가 있다.
    • em.persist(entity) 로 영속화
    • em.remove(entity) 로 제거
  • 공유할 수 있다.
    • 참조 값 공유할 수 있다. 이것이 공유 참조라고 한다.
    • ex : 멤버 엔티티가 있으면 다른 엔티티에서 얼마든지 멤버 엔티티 참조 가능

값 타입 특징

  • 식별자 없음
  • 생명 주기를 엔티티에 의존함
    • 스스로 생명주기를 가지지 않고 엔티티에 의존한다. 의존하는 엔티티를 제거하면 같이 제거된다.
  • 공유하지 않는 것이 안전하다.
    • 엔티티 타입과는 다르게 공유하지 않는 것이 안전하다. 대신 값을 복사하자 ‼️
    • 오직 하나의 주인만 관리해야 함.
    • 불변 객체로 만드는 것이 안전

엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만드는 실수는 범하지 말자!!!

profile
💻 많이 짜보고 많이 경험해보자 https://lsj8367.tistory.com/ 블로그 주소 옮김

0개의 댓글