JPA 값타입...

Yangray·2021년 9월 30일
0

JPA

목록 보기
5/5

JPA의 데이터 타입 분류

JPA에서 데이터 타입은 크게 2가지로 나눌 수 있다.

엔티티 타입

  • @Entity로 정의하는 객체
  • 데이터가 변해도 식별자로 지속해서 추적이 가능하다.
    EX) 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식이 가능함.

값 타입

  • int, Integer, String 처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
  • 식별자가 없고 값만 있기 때문에 변경시 추적이 불가능하다.
    EX) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체
int a = 100;
int b = a;
a = 200;
System.out.println("a : " + a);
System.out.println("b : " + b);

결과 : a : 100 
      b: 200

값 타입 분류

기본값 타입

  • 자바 primitive 타입(int,double...)
  • 자바 Reference 타입(Integer, Long)
  • String
  • 생명주기를 엔티티에 의존한다. EX) 회원을 삭제하면 이름, 나이 필드도 함께 삭제된다.
  • 값 타입은 공유하면 안된다 EX) 회원 이름 변경시 다른 회원의 이름도 함께 변경되면 안된다.
  • ❌ 자바의 기본 타입은 절대 공유되서는 안된다.
    -- 기본 타입은 항상 값을 복사한다.
    EX) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체
int a = 100;
int b = a;
b = 200;
System.out.println("a : " + a);
System.out.println("b : " + b);

결과 : a : 100 
      b: 200

-- Reference type 클래스는 공유 가능한 객체이지만 변경할 수 없다.

  Integer num1 = 100;  // Integer 객체 생성
       Integer num2 = num1; // num1과 num2는 같은 Integer 객체를 참조

       System.out.println(num1);  // 출력: 100
       System.out.println(num2);  // 출력: 100

       num1 = 200;  // num1이 새로운 Integer 객체를 참조하게 됨 (기존 객체는 변경되지 않음)

       System.out.println(num1);  // 출력: 200 (num1은 새로운 객체를 참조)
       System.out.println(num2);  // 출력: 100 (num2는 여전히 원래 객체를 참조)

(그래서 개발하면서 신경 쓰지 않고 개발을 했던 것 같다....)

임베디드 타입

  • 새로운 값 타입을 직접 정의할 수 있다.
  • JPA는 임베디드 타입이라 함
  • 주로 기본 값 타입을 모아 만들어서 복합 값 타입이라고도 부른다.
  • int, String과 같은 값 타입이다.
    EX) 회원 엔티티는 이름, 근무 시작일, 근무 종료일, 주소 도시, 주소 번지, 주소 우편번호를 갖는다.

근무 기간과 주소는 다른 엔티티에서 사용될 수 있어 코드가 중복이 될 수 있으므로
회원 엔티티는 근무 시작일 ,종료일을 근무 기간으로 묶을 수 있고, 주소 도시, 번지, 우편번호는 주소로 묶을 수 있다.

// Member 클래스
//@Embedded: 값 타입을 사용하는 곳에 표시
  //근무 기간간 Period
    @Embedded
    private Period workperiod;


    //주소 Address
    @Embedded
    private Address homeaddress;
    
//Address 클래스
//@Embeddable: 값 타입을 정의하는 곳에 표시
@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;
    }
    ...
}
//@Embeddable: 값 타입을 정의하는 곳에 표시
@Embeddable
public class Period {

    private LocalDateTime startDate;
    private LocalDateTime endDate;

    public Period() {}
    
	...
}

임베디드 타입은 기본 생성자가 필수이다.

임베디드 타입의 장점

  • 재사용
  • 높은 응집도
  • 해당 값 타입만 사용하는 의미 있는 메소드를 만들 수 있다.
  • 임베디드 타입을 포함한 모든 값 타입은 값 타입을 소유한 엔티티에 생명주기를 의존한다.
    -- 값 타입을 소유한 엔티티의 값들을 변경하면 자동 UPDATE...(CASCADE.ALL , 변경 유사함)

임베디드 타입과 테이블 매핑

  • 임베디드 타입은 엔티티의 값일 뿐이기 때문에 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.
  • 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능해짐

임베디드 타입과 연관관계

  • 임베디드 값은 엔티티를 가져도 된다. foreign key 값만 가지면 가능해진다.

만약에 한 엔티티에서 같은 값 타입을 사용한다고 하면???

@Embedded
   // 집 주소 Address
    private Address homeaddress;

    //사무실 주소 Address
    @Embedded
    private Address officeaddress;

예를들어 Member 엔티티에서 집주소와 사무실 주소의 주소 값 타입을 사용한다고 가정해보자
어떻게 될까? 원하는대로 각각 생성이 될까???
그렇지 않다!!!!
Caused by: org.hibernate.MappingException
컬럼명이 중복이 되어 Exception이 발생하게된다.

@Embedded
   // 집 주소 Address
    private Address homeaddress;

    //사무실 주소 Address
    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name="city",
                    column=@Column(name="office_city")),
            @AttributeOverride(name="street",
                    column=@Column(name="office_street")),
            @AttributeOverride(name="zipcode",
                    column=@Column(name="office_zipcode"))
    })
    private Address officeaddress;

@AttributeOverride: 속성 재정의를 사용해서 컬럼명 속성을 재정의 하자!!

임베디드 타입과 null

  • 임베디드 타입의 값이 null이면 매핑한 컬럼 값은 모두 null !!!

값 타입과 불변 객체

값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고
만든 개념이다. 따라서 값 타입은 단순하고 안전하게 다
룰 수 있어야 한다.

값 타입 공유 참조

 Address address = new Address("city", "street", "100");
            Member memberA = new Member();
            memberA.setUsername("UserA");
            memberA.setHomeaddress(address);
            em.persist(memberA);

            Address newAddress = new Address(address.getCity(),address.getStreet(), address.getZipcode());

            Member memberB = new Member();
            memberB.setUsername("UserB");
            memberB.setHomeaddress(address);
            em.persist(memberB);

결과

여기서 UserA의 도시만 변경해보자!!

// UserA의 도시를 city -> newCity로 변경
memberA.getHomeaddress().setCity("newCity");

결과

😱😱😱 UserA만 변경하고자 했는데, UserB까지 변경이 되는 오류가 발생 해버렸다!!!!!

int a = 100;
int b = a;
b = 200;
System.out.println("a : " + a);
System.out.println("b : " + b);

결과 : a : 100 
      b: 200

이유는 값 타입 복사는 primitive type 같이 복사하면 값이 복사가 되는 것이 아니라

위 그림 처럼 값 대신 인스턴스 주소값을 복사해서 사용해서 같은 UserA와 UserB는 같은 인스턴스를 바라보게 된다.
따라서 값 타입의 실제 인스턴스인 값을 공유하는 것은 위험하다.

객체 타입의 한계

  • 항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다.
  • 문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입이다.
  • 객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다.
  • 따라서 객체의 공유 참조는 피할 수 없는 치명적 문제이다.

불변 객체

  • 객체의 공유 참조는 피할 수 없는 치명적인 문제라고 했다.
  • 피할 수 없으면 애초에 객체 타입 자체를 수정할 수 없게 만들어 문제를 원천 차단해야한다.
  • 따라서 값 타입은 불변 객체로 설계해야한다. (불변 객체 : 생성 시점 이후 절대 값을 변경할 수 없는 객체)
    -- 생성자로만 값을 설정하고 수정자(setter)를 만들지 않아야한다.
    만약에 만들더라도 private로 사용되지 못하게 처리!!!
    불변이라는 작은 제약으로 부작용이라는 큰 재앙을 막을 수 있다

값 타입의 비교

  • 동일성(identity)비교 : 인스턴스의 참조 값을 비교, == 사용
  • 동등성(equivalence) 비교 : 인스턴스의 값을 비교, equals() 사용

    값 타입은 a.equals(b)를 사용해서 동등성 비교를 해야한다.
    equals() 메소드를 사용할때 재정의를 하지 않으면 default가 == 비교이기 때문에
    사용하기 전에 메소드를 적절하게 재정의를 하고 사용해야 된다.

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

hash값을 비교하여 동등성을 비교한다.

정리

엔티티 타입의 특징

  • 식별자O
  • 생명 주기 관리
  • 공유

값 타입의 특징

  • 식별자X
  • 생명 주기를 엔티티에 의존
  • 공유하지 않는 것이 안전(복사해서 사용)
  • 불변 객체로 만드는 것이 안전

값 타입은 정말 값 타입이라 판단될 때만 사용해야한다.
엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안된다.
식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티

profile
시작은 미약하나 그 끝은 창대하리라

0개의 댓글

관련 채용 정보