JPA 스터디 (9장)

31649 단어 JPAsutdyJPA

9장 값 타입

  • 엔티티 타입 : @Entity 로 정의하는 객체
  • 값 타입 : int, Integer, String 같이 단순히 값으로 사용하는 자바 기본 타입이나 객체
    • 기본값 타입
      • 자바 기본 타입 (예 : int, double)
      • 래퍼 클래스 (예 : Integer)
      • String
    • 임베디드 타입 (복합 값 타입) : 사용자가 직접 정의한 값 타입
    • 컬렉션 값 타입 : 하나 이상의 값 타입 저장 시 사용

9.1 기본값 타입

@Entity
public class Member {
	
	@id @GeneratedValue
	private Long id;
	private String name; //값 타입
	private int age; //값 타입 
	...
}

//name, age 속성은 식별자 값도 없고 생명주기도 Membr 엔티티에 의존한다.

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

@Entity
public class Member {
	
	@Id @GeneratedValue
	private Long id;
	private String name;

	@Embedded Period workPeriod; //근무 기간
	@Embedded Address homeAddress; //집 주소

}

@Embeddable
public class Period {
	@Temporal(TemporalType.DATE) java.util.Date startDate;
	@Temporal(TemporalType.DATE) java.util.Date endDate;
	
	public boolean isWork(Date date) {
		//값 타입을 위한 메소드 정의 가능
	}

}

@Embeddable
public class Address {
	
	@Column(name="city") //매핑 컬럼 정의
	private String city;
	private String street;
	private String zipcode;

}
  • 임베디드 타입을 포함한 모든 값 타입은 엔티티의 생명주기에 의존하므로 엔티티와 임베디드 타입의 관계를 UML로 표현하면 컴포지션 관계가 된다.

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

    • 임베디드 타입은 엔티티의 값일 뿐이므로 값이 속한 엔티티의 테이블에 매핑 한다.
    • 임베디드 타입 덕에 객체와 테이블을 아주 세밀하게 매핑 가능 (주소나 근무기간 같은 값 타입 클래스를 만들어 더 객체지향적으로 개발 가능)
  • 임베디드 타입과 연관관계

@Entity
public class Member {
	
	@Embedded Address address; //임베디드 타입 포함
	@Embedded PhoneNumber phoneNumber;
	//...
}

@Embeddable
public class Address {
	String street;
	String city;
	String state;
	@Embedded Zipcode zipcode; //임베디드 타입 포함
}

@Embeddable
public class Zipcode {
	String zip;
	String plusFour;
}

@Embeddable
public class PhoneNumber {
	String areaCode;
	String localNumber;
	@ManyToOne PhoneServiceProvider provider; //엔티티 참조
}

@Entity
public class PhoneServiceProvider {
	@Id String name;
	...
}
  • @AttributeOverride
@Entity
public class Member {
	
	@Id @GeneratedValue
	private Long id;
	private String name;

	@Embedded Address honeAddress;

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

}

//위와 같이 설정 후 auto DDL 작동 시
//COMPANY_* 의 이름을 가진 컬럼들이 생성 된다. 
  • 임베디드 타입과 null
    • member.setAddress(null); em.persist(member);
    • 회원테이블의 주소와 관련된 CITY,STREET,ZIPCODE 컬럼은 모두 null 이 된다.

9.3 값 타입과 불변 객체

  • 값 타입 공유 참조

    • 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유 시 문제가 발생한다.

      member1.setHomeAddress(new Address("OldCity"));
      Address address = member1.getHomeAddress();
      
      address.setCity("NewCity"); //회원1의 address 값을 공유해서 사용
      member2.setHomeAddress(address);
      
      -----------------------------------------------
      이거 회원1의 city 값도 NewCity 로 바뀌게 된다. 왜냐? 같은 인스턴스를 참조하기 때문
      해결방법? 값을 복사하여 사용할 것
  • 값 타입 복사

    member1.setHomeAddress(new Address("OldCity"));
    Address address = member1.getHomeAddress();
    
    Address newAddress = address.clone(); //값 복사!
    
    newAddress.setCity("NewCity"); 
    member2.setHomeAddress(newAddress);
    • 객체를 대입할 때 마다 인스턴스를 복사해서 대입 시 공유 참조를 피할 수 있는데, 문제는 복사하지 않고 원본의 참조 값을 직접 넘기는 것을 막을 방법이 없다는 것이다.
    • 객체 공유 참조는 피할 수 없다. 가장 단순한 근본적인 해결책으로는 객체 값 수정을 못하게 막아버리면 된다. (Setter 를 막아버린다는 의미), Lombok 을 사용한다면 @Date 대신 @Getter만 사용 하기
  • 불변 객체 (한번 만들면 절대 변경할 수 없는 객체)

    • 값 타입은 부작용을 원천 차단하기 위해 될 수 있으면 불변 객체로 설계해야 한다.
    • 생성자로만 값을 설정 하고 수정자를 만들지 않으면 된다! (스프링에서 생성자 주입 기법을 생각해보라!)
    • 불변이라는 작은 제약으로 부작용이라는 큰 재앙을 막을 수 있다!

9.4 값 타입의 비교

  • 자바가 제공 하는 객체 비교 (3장에서 잠깐 언급했던 내용)
    • 동일성 : 인스턴스의 참조값을 비교, == 사용
    • 동등성 : 인스턴스의 값을 비교, equals() 사용
    • equals() 와 hashCode() 재정의 하여 사용! ( hashCode() 재정의 안하면?→ 해시 사용하는 컬렉션에서 문제가 발생한다)

9.5 값 타입 컬렉션

@Entity
public class Member {

	@Id @GeneratedValue
	private Long id;
	
	@Embedded
	private Address homeAddress;

	@ElementCollection //값 타입 컬렉션 이용 시 사용하는 어노테이션
	@CollectionTable(name = "FAVORITE_FOODS", //값 타입 컬렉션 이용 시 사용하는 어노테이션
		joinColumns = @JoinColumn(name = "MEMBER_ID"))
	@Column(name="FOOD_NAME")
	private Set<String> favoriteFoods = new HashSet<>();
	
	@ElementCollection
	@CollectionTable(name = "ADDRESS",
		joinColumns = @JoinColumn(name = "MEMBER_ID"))
	private List<Address> addressHistory = new ArrayList<>();
	...
}

@Embeddable
public class Address {
	
	@Column
	private String city;
	private String street;
	private String zipcode;
	...
}
  • 값 타입 컬렉션 사용
Member member = new Member();

//임베디드 값 타입
member.setHomeAddress(new Address("Soonworld","순해수욕장","123-4567"));

//기본값 타입 컬렉션
member.getFavoriteFoods().add("짬뽕");
member.getFavoriteFoods().add("짜장");
member.getFavoriteFoods().add("탕수육");

//임베디드 값 타입 컬렉션
member.getAddressHistory().add(new Address("서울","강남","123-1234"));
member.getAddressHistory().add(new Address("서울","강북","321-4321"));
					
em.persist(member);
-------------------------------------
실제 DB에 실행되는 SQL

member : INSERT 1번
member.homeAddress : 임베디드 값이므로 회원 테이블 저장 시 포함
member.favoriteFoods : INSERT 3번
member.addressHistory : INSERT 2
//실행된 실제 SQL
INSERT INTO MEMBER(ID, CITY, STREET, ZIPCODE) VALUES(1, 'Soonworld', '순해수욕장', '123-4567')

INSERT INTO FAVORITE_FOODS(MEMBER_ID, FOOD_NAME) VALUES(1,"짬뽕");
INSERT INTO FAVORITE_FOODS(MEMBER_ID, FOOD_NAME) VALUES(1,"짜장");
INSERT INTO FAVORITE_FOODS(MEMBER_ID, FOOD_NAME) VALUES(1,"탕수육");

INSERT INTO ADDRESS(MEMBER_ID, CITY, STREET, ZIPCODE) VALUES(1, '서울', '강남', '123-1234')
INSERT INTO ADDRESS(MEMBER_ID, CITY, STREET, ZIPCODE) VALUES(1, '서울', '강북', '321-4321')
 

//값 타입 컬렉션은 영속성 전이(Cascade) + 고아 객체 제거 기능을 필수로 가진다.
  • 값 타입 컬렉션 조회 시 페치전략 (Default LAZY)

  • 값 타입 컬렉션의 제약사항

    • 값 타입 컬렉션에 변경 사항이 발생하면, 값 타입 컬렉션이 매핑된 테이블의 연관된 모든 데이터를 삭제하고, 현재 값 타입 컬렉션 객체에 있는 모든 값을 데이터베이스에 다시 저장 한다. (연관데이터 삭제 후 저장)
    • 실무에서 값 타입 컬렉션이 매핑된 테이블에 데이터가 많을 시 값 타입 컬렉션 대신 "일대다 관계" 를 고려 해야 한다. + (영속정 전이 + 고아 객체 제거)
    • 값 타입 컬렉션을 사용할 때는 모두 삭제하고 다시 저장 하는 최악의 시나리오도 고려하면서 사용 해야 함

9.6 정리

  • 엔티티 타입

    • 식별자(@Id) 존재
    • 생명 주기 존재
    • em.persist(entity) 로 영속화 한다.
    • em.remove(entity) 로 제거 한다.
    • 공유 할 수 있다.
      • 공유 참조
  • 값 타입

    • 식별자 없음
    • 생명 주기를 엔티티에 의존함
    • 공유하지 않는 것이 안전함!
      • 불변 객체로 만들어 사용
  • 값 타입은 정말 값 타입이라고 판단될 때만 사용 해야 한다! 위에 나열한 예제들 처럼 판단 되는 경우에만 사용 하기..!

  • 특히 엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨..! 혼동 주의..!

좋은 웹페이지 즐겨찾기