JPA에서 데이터 타입은 크게 2가지로 나눌 수 있다.
int a = 100;
int b = a;
a = 200;
System.out.println("a : " + a);
System.out.println("b : " + b);
결과 : a : 100
b: 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는 여전히 원래 객체를 참조)
(그래서 개발하면서 신경 쓰지 않고 개발을 했던 것 같다....)
근무 기간과 주소는 다른 엔티티에서 사용될 수 있어 코드가 중복이 될 수 있으므로
회원 엔티티는 근무 시작일 ,종료일을 근무 기간으로 묶을 수 있고, 주소 도시, 번지, 우편번호는 주소로 묶을 수 있다.
// 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() {}
...
}
임베디드 타입은 기본 생성자가 필수이다.
@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: 속성 재정의를 사용해서 컬럼명 속성을 재정의 하자!!
값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고
만든 개념이다. 따라서 값 타입은 단순하고 안전하게 다
룰 수 있어야 한다.
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는 같은 인스턴스를 바라보게 된다.
따라서 값 타입의 실제 인스턴스인 값을 공유하는 것은 위험하다.
값 타입은 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값을 비교하여 동등성을 비교한다.
값 타입은 정말 값 타입이라 판단될 때만 사용해야한다.
엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안된다.
식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티