Ep9. 값 타입

yumyeonghan·2023년 2월 7일
0

JPA

목록 보기
9/10

🍃이 글은 inflearn에서 김영한의 스프링 부트와 JPA 실무 완전 정복 로드맵을 학습하고 작성한 것입니다.🍃

기본값 타입

JPA의 최상위 데이터 타입 분류 2가지

  • 1. 엔티티 타입

    • @Entity로 정의하는 객체

    • 데이터가 변해도 식별자로 지속해서 추적 가능

    • 예)회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능

  • 2. 값 타입

    • int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체

    • 식별자가 없고 값만 있으므로 변경 시 추적 불가

    • 예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체

값 타입 분류 3가지

  • 1. 기본값 타입

    • 자바 기본 타입 (int, double)
    • 래퍼 클래스 (Integer, Long)
    • String
  • 2. 임베디드 타입

    • x좌표, y좌표를 가진 Position 클래스를 만들어서 커스텀하게 값처럼 사용
  • 3. 컬렉션 값 타입

    • 컬렉션에 값 타입을 넣어서 사용
  • 특징

    • 생명 주기를 엔티티에 의존한다.

    • 회원 엔티티를 삭제하면 String name, int age 등의 값 타입이 전부 삭제된다.

    • 값 타입은 절대로 공유하면 안 된다.

    • 내 회원 이름을 변경할 때 다른 회원의 이름도 함께 변경되는 사이드 이펙트 문제가 일어나면 안 된다.

임베디드 타입

  • 멤버 엔티티를 위 그림보다 아래 그림처럼 근무 기간, 집 주소로 좀 더 추상화 해서 설계하면 더 좋다.

  • 이렇게 컬럼들을 묶어낼 수 있는 것이 임베디드 타입이다.

  • 근무 기간과 집 주소 클래스를 생성해 준다.

사용 방법

@Embeddable
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Address {

	// 기본 생성자 꼭 필요하다.
	
	private String city;
	private String street;
	
	@Column(name = "ZIPCODE") // 가능
	private String zipcode;
}
  • @Embeddable: 값 타입을 정의하는 곳에 표시한다.
  • 기본 생성자는 필수이다.
  • @Column 속성도 사용할 수 있다.

@Embeddable
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Period {

	// 기본 생성자 꼭 필요하다.
	
	private LocalDateTime startDate;
	private LocalDateTime endDate;

	pubilc boolean isWork() {
		//지금 근무 기간인지 체크하는 의미 있는 메소드 추가 가능
	}
}
  • @Embeddable: 값 타입을 정의하는 곳에 표시한다.
  • 기본 생성자는 필수이다.
  • isWork()같은 의미 있는 메소드를 추가할 수 있다.

@Entity
public class Member {

	@Id
	@GeneratedValue
	@Column(name = "MEMBER_ID")
	private Long id;

	@Column(name = "USERNAME")
	private String username;

	//기간
 	@Embedded
	private Period period;

	//주소
	@Embedded
	private Address address;
}
  • @Embedded: 값 타입을 사용하는 곳에 표시한다.

임베디드 타입의 장점

  • 재사용이 가능하다.

  • 관련된 필드와 의미 있는 메소드로 이루어져서 응집도가 높다.

  • 임베디드 타입을 포함한 모든 값 타입은, 이 값 타입을 소유한 엔티티에 생명주기를 의존한다.

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

  • 임베디드 타입은 엔티티의 값일 뿐이다.

  • 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.

  • 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능하다.

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

값 타입과 불변 객체

// 위에서 만든 임베디드 타입 Address 객체 
Address address = new Address("city", "street", "1000");

Member member1 = new Member();
member1.setAddress(address);

Member member2 = new Member();
member2.setAddress(address);

member1.getAddress().setCity("newCity");
  • 이렇게 돼버리면 member2의 도시 주소도 "newCity"로 바뀌는 사이드 이펙트 문제가 발생한다.

  • 그래서 임베디드 타입을 불변 객체로 만들어서 변경 자체를 불가능하게 해야 한다.


불변 객체

  • 불변 객체는 생성 시점 이후 절대 값을 변경할 수 없는 객체이다.

  • 값 타입은 불변 객체(immutable object)로 설계해야 한다.

  • 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 된다.

  • Integer, String이 자바가 제공하는 대표적인 불변 객체이다.

위에서 만든 임베디드 타입 Address, Period 클래스

  • 기본 생성자와 모든 필드 값을 파라미터로 받는 생성자를 만들고 setter를 만들지 않는다.
  • 따라서 값을 변경하는 것이 불가능해지기 때문에 생성할 때 항상 new로 새로 만들어야 한다.
  • 사이드 이펙트 문제가 발생하지 않는다.

값 타입의 비교

  • 동일성 비교: 인스턴스의 참조 값을 비교한다. (== 사용)

  • 동등성 비교: 인스턴스의 값을 비교한다. (equals() 사용)

  • 값 타입은 new로 생성하기 때문에 a.equals(b)를 사용해서 동등성 비교를 해야 한다.

  • 그러기 위해선 값 타입의 equals() 메소드를 오버라이딩 해야 true가 된다.

  • 참고로 String, Integer 같은 래퍼 클래스는 equals() 메소드가 이미 오버라이딩 돼 있다.

intellj가 제공하는 자동 완성 기능을 사용하자.

  • generate(command + n)에서 equals() and hashCode()를 적용한다.
  • equals()를 구현할 때 hashCode()도 그것에 맞게 구현 돼야 한다.

값 타입 컬렉션

  • 값 타입을 컬렉션에 담아서 사용한다.

  • 하지만 값 타입 컬렉션 대신에 일대다 관계를 고려하자.

  • 일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용하면 된다.

  • 영속성 전이 + 고아 객체 제거를 사용해서 생명주기가 엔티티에 의해 관리되는 값 타입 컬렉션처럼 사용하자.


@Entity
public class Member {

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "ADDRESS_ID")
    private List<AddressEntity> addressHistory = new ArrayList<>();
    
}
  • 영속성 전이 + 고아 객체 제거를 사용해서 값 타입 컬렉션처럼 사용하는 Member 클래스
  • AddressEntity의 생명주기가 Member에 의해 관리된다.
  • 양방향 매핑으로 해도 상관없다. 상황에 맞게 사용하자.

@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "ADDRESS")
public class AddressEntity {

    @Id @GeneratedValue
    private Long id;

	@Embedded
    private Address address; // 값 타입 
}
  • 값 타입을 엔티티화 한 AdressEntity 클래스
  • 여기서 임베디드 값 타입인 Adress를 사용한다.

Member member = new Member();

member.getAddressHistory().add(new AddressEntity("city1", "street1", "1000"));
member.getAddressHistory().add(new AddressEntity("city2", "street2", "2000"));
  • 이런식으로 값 타입 컬렉션처럼 사용할 수 있다.
profile
웹 개발에 관심 있습니다.

0개의 댓글