1. 기본값 타입
- 생명주기를 엔티티에 의존
- 값 타입은 공유되지 않는다. (변경시 다른 값도 변경되면 안된다.)
- 래퍼 클래스나 String같은 클래스는 공유가능한 객체지만 변경안됨.
2. 임베디드 값 타입(복합 값 타입)
재사용 가능한 값 타입 객체를 정의하여 엔티티의 일부분으로 포함시킨다.
2-1) 임베디드 값 타입 사용법
✅임베디드 값 타입 사용 예시
위 그림을 차근차근 설명해보자면,
✔️ 멤버 엔티티는 이름, 근무 시작일(startDate), 근무 종료일(endDate), 주소 도시(city), 주소 번지(street), 주소 우편번호(zipcode)를 가진다.
근무 시작일, 근무 종료일은 기간으로 묶을 수 있다.
주소 도시, 주소 번지, 주소 우편번호는 주소로 묶을 수 있다.
✔️ @Embeddable 애노테이션을 사용한 Period, Address 클래스를 만들어준다. (새로운 값 타입 직접 정의)
✔️ 멤버 엔티티에 @Embedded 애노테이션을 사용해서 Period, Address 값 타입을 넣어준다.
✔️ 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.
✅임베디드 값 타입 정의 코드
@Embeddable
@Getter
@Setter
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
public Period() {
}
public Period(LocalDateTime startDate, LocalDateTime endDate) {
this.startDate = startDate;
this.endDate = endDate;
}
}
@Embeddable
@Getter
@Setter
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 을 사용하여 임베디드 값 타입을 정의한다.
- 기본 생성자가 필수다.
✅Member 엔티티
@Entity
@Getter
@Setter
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@Embedded
private Period workPeriod;
@Embedded
private Address homeAddress;
}
- 임베디드 값 타입을 사용하는 곳에 @Embedded를 붙인다.
✅main 메서드
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = new Member();
member.setUsername("ururuwave");
member.setHomeAddress(new Address("city","street","10000"));
member.setWorkPeriod(new Period(LocalDateTime.of(2024, 1, 1,9,0),LocalDateTime.of(2024,7,1,9,0)));
em.persist(member);
tx.commit();
}catch (Exception e){
tx.rollback();
e.printStackTrace(); //예외 출력
}finally {
em.close();
}
}
- 단순히 멤버 인스턴스 생성해서 값 입력하는 코드다.
✅DB 확인
- 임베디드 타입은 엔티티의 값일 뿐이다.
- Period, Address 값 타입을 그대로 필드명으로 쓰는게 아니라 임베디드 값 타입 안에 있는 것들을 필드명으로 사용한다. -> 그래서 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블이 동일하다.
2-2) @AttributeOverride (속성 재정의)
- 한 엔티티에서 같은 값 타입을 사용하면 컬럼명이 중복되기때문에 오류가 난다.
- @ AttributeOverrides, @AttributeOverride를 사용해서 컬럼명 속성을 재정의한다.
✅예시
@Embedded
private Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "WORK_CITY")),
@AttributeOverride(name = "street", column = @Column(name = "STREET_CITY")),
@AttributeOverride(name = "zipcode", column = @Column(name = "ZIPCODE_CITY"))})
private Address workAddress;
- Address 값 타입을 중복 사용하고 있다.
- 속성을 재정의해서 컬럼명이 중복되지 않도록 해준다.
2-3) 임베디드 값 타입 특징
✔️여러 엔티티에서 재사용할 수 있다
- 코드 중복을 줄이고 일관성 유지를 할 수 있다.
✔️ 독립적인 생명 주기가 없다.
-임베디드 값 타입은 포함된 엔티티와 함께 생성, 수정, 삭제된다.
✔️ null 가능
-임베디드 타입의 값이 null이면 매핑한 컬럼값이 모두 null이 된다.
✔️ JPA 스펙의 제한 사항때문에 임베디드 값 타입 필드에 not null 제약조건을 거는 것이 불가능하다.
🤔Q. JPA 스펙에서 기능을 막아둔 이유가 있지 않을 까? 왜 이렇게 만들었을까?
💡A1. 단점이 있다.
- 임베디드 값 타입이 여러 엔티티에 사용될 때, 각각의 엔티티에 일관된 제약 조건을 관리하는 것이 어렵다.
- 일관성 문제로 개발자의 실수나 데이터 불일치로 이어질 수 있다.
임베디드 값 타입은 독립적인 엔티티가 아니라, 특정 필드가 NOT NULL 조건을 가질 경우 값 타입의 모든 필드가 올바르게 초기화되지 않으면 엔티티를 저장할 수 없다.
💡A2. 하지만! 특정 상황에서 NOT NULL 제약 조건이 필요하다면 우회해서 사용할 수 있다.
- @AttributeOverride를 사용하여 임베디드 값 타입 필드를 포함하는 엔티티에서 개별 필드에 대해 속성을 재정의한다.
2-4) 임베디드 값 타입 비교
인스턴스가 달라도 그 안에 값이 같으면 같은 것으로 봐야한다.
객체를 == 비교를 하면 인스턴스의 참조값을 비교하기 때문에 인스턴스 안의 값이 같아도 false를 반환한다.
값 타입 안에 equals(), hashCode()를 오버라이드해야한다.
intelliJ에서 'Alt+Insert'를 눌러서 equlas(), hashCode()를 자동 생성해주면 된다.
💡참고로 equals() 메서드를 오버라이드할 때, hashCode() 메서드를 같이 오버라이드 해야한다.
왜냐하면, equlas()에 의해 동등하다고 판단되면 두 객체의 hashCode()값도 반드시 같아야하기 때문이다.
그래서 equals()와 hashCode() 메서드는 일관되게 동작해야한다.
💡임베디드 값 타입 뿐만 아니라 String, Integer와 객체 타입 값들도 모두 equals()로 비교해줘야한다.
Java의 기본 제공 클래스는 Java 표준 라이브러리에서 이미 equals()메서드를 적절히 오버라이드 하고 있어서 재정의할 필요없다.
✅equals(), hashCode() 재정의
@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);
}
✅equals()로 비교하기
public class ValueMain {
public static void main(String[] args) {
int a = 10;
int b = 10;
System.out.println("a == b: " + (a == b)); //true
//address1과 address2는 인스턴스의 값이 같다.
Address address1 = new Address("city", "street", "10000");
Address address2 = new Address("city", "street", "10000");
//'==' 비교는 인스턴스의 참조값을 비교한다.
System.out.println("address1 == address2: " + (address1 == address2)); //fasle
//'equals'는 인스턴스의 값을 비교한다. **값 타입에서 equals()메소드를 재정의해야함.
System.out.println("address1 equals address2: " + (address1.equals(address2))); //true
}
}
'인프런 김영한 강의 정리 > 자바 ORM 표준 JPA 프로그래밍 기본편' 카테고리의 다른 글
JPA 기본 | 데이터 타입(3) - 값 타입 컬렉션 (0) | 2024.07.14 |
---|---|
JPA 기본 | 데이터 타입(2) - 값 타입 공유와 불변객체 (0) | 2024.07.14 |
JPA 기본 | 영속성 전이(CASCADE)와 고아 객체 (0) | 2024.07.13 |
JPA 기본 | 즉시 로딩과 지연 로딩 (0) | 2024.07.13 |
JPA 기본 | 프록시 (즉시 로딩, 지연 로딩을 이해하기 위한) (0) | 2024.07.13 |