본문 바로가기
Develop/JPA

[JPA/기본편] 값 타입

by J-rain 2024. 3. 20.

 

값 타입

JPA의 데이터 타입은 크게 엔티티 타입, 값 타입 두개로 분류 가능

  • 엔티티 타입
    • @Entity 로 정의하는 객체
    • 식별자 있음
      • 식별자로 구별하고 지속 추적 가능
    • 생명 주기가 있음
      • 생성, 영속화, 소멸하는 생명 주기 존재
    • 공유 가능
      • 참조 값 공유할 수 있음 => 공유 참조
  • 값 타입
    • int, Integer, String 처럼 단순히 값으로 사용하는 자바 기본 타입 또는 객체
    • 식별자가 없음
    • 생명 주기를 엔티티에 의존
      • ex) 회원을 삭제하면 이름, 나이필드로 함께 삭제
    • 공유하지 않는 것이 안전
      • 대신 값 복사해서 사용
      • 오직 하나의 주인만이 관리해야 함
      • 불변 객체로 만드는 것이 안전함
    • 다시 3 가지로 나눌 수 있음
      • 기본값 타입: 자바 기본 데이터 타입
        • 자바 기본 타입 (int, double, …)
        • 래퍼 클래스 (ex. Integer)
        • String
      • 임베디드 타입: JPA에서 사용자가 직접 정의한 값 타입
      • 컬렉션 값 타입: 하나 이상의 값 타입 저장시 사용

 

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

  • JPA에서 새로운 값 타입 직접 정의해서 사용하는 것
  • 임베디드 타입도 int, String 처럼 값 타입임
  • 임베디드 타입 사용시 다음 2가지 어노테이션 필요 (둘 중 하나는 생략 가능)
    •  @Embeddable  : 값 타입 정의하는 곳에 표시
    •  @Embedded  : 값 타입 사용하는 곳에 표시 
// Embedded type 사용하지 않은 경우
@Entity
public class MemberWithoutEmbeddedType {
  @Id
  @GeneratedValue
  private Long id;

  private String name;

  // 근무 기간
  @Temporal(TemporalType.DATE) 
  Date startDate;
  @Temporal(TemporalType.DATE)
  Date endDate;

  // 집 주소
  private String city;
  private String street;
  private String zipcode;

  ...
}

...

// 근무 기간, 집 주소 Embedded type 만들어서 사용한 경우
@Entity
public class MemberWithEmbeddedType {
    @Id
  @GeneratedValue
  private Long id;

  private String name;

  // 근무 기간
  @Embedded
  Period workPeroid;

  // 집 주소
  @Embedded
  Address homeArrdress;
  
  ...
}

...

// 기간 embedded type
@Embeddable
public class Period {
  @Temporal(TemporalType.DATE)
  Date startDate;
  
  @Temporal(TemporalType.DATE)
  Date startDate;

  ...

  public boolean isWork(Date date) {
    // 값 타입을 위한 메소드 정의 가능
  }

  ...
}

// 주소 embedded type
@Embeddable
public class Address {
  @Column(name="city")  // 매핑할 컬럼 정의 가능
  private String city;

private String street;

private String zipcode;

...
}
  • 임베디드 타입은 기본 생성자가 필수
  • 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.

 

@AttributeOverride: 속성 재정의

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

  • @AttributeOverride 를 사용하여 임베디드 타입에 정의한 매핑정보 재정의 가능
@Entity
public class Member {
  @Id
	private Long id;
  private String name;

  @Embedded
	private Address homeAddress;

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

 

 

 

값 타입과 불변 객체

 

값 타입 공유 참조

  • 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험함
  • 공유 참조로 인한 side effect 발생할 수 있으므로 값을 복사해서 사용해야 함

불변 객체

  • 객체 값 수정 못하게 하면 사이드 이펙트 차단 가능
  • 따라서, 값 타입은 될 수 있으면 immutable object 로 설계해야 함
    • ex) 생성자로만 값 설정하고 수정자(세터) 를 만들지 않으됨
  • Integer, String 은 자바가 제공하는 대표적 불변 객체

 

값 타입의 비교

  • 자바가 제공하는 객체 비교는 다음 2가지이다.
    • 동일성(identity) 비교: 인스턴스의 참조 값을 비교, == 사용
    • 동등성(equivalnce) 비교: 인스턴스의 을 비교,  equals()  사용
  • 값 타입 비교시에는  equals()  를 이용하여 동등성 비교 해야 한다.
  • 직접 값 타입 정의할 경우  equals()  메소드 재정의해줘야 한다.
    •  equals()  재정의 시  hashCode()  도 재정의 해야한다.
    • 안하면 컬렉션 정상 동작 안함

 

값 타입 컬렉션

  • 값 타입을 하나 이상 저장하려면 컬렉션에 넣고  @ElementCollection  @CollectionTable  어노테이션을 사용
  • 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
    • 따라서 컬렉션을 저장하기 위한 별도의 테이블이 필요하다.
@Entity
public class Member {
  @Id
  @GeneratedValue
  private Long id;

  @Embedded
  private Address homeAddress;

  @ElementCollection
  @CollectionTable(name = "FAVORITE_FOOD",
    joinColumns = @JoinColumn(name = "MEMBER_ID"))
  @Column(name = "FOOD_NAME")
  private Set<String> favoriteFoods = new HashSet<String>();

  @ElementCollection
  @CollectionTable(name = "ADDRESS",
    joinColumns = @JoinColumn(name = "MEMBER_ID"))
  private List<Address> addressHistory = new ArrayList<Address>();

  ...
}

@Embedaable
public class Adress {
  @Column
  private String city;

  private String street;

  private String zipcode;

  ...
}

 

  • favoriteFoods처럼 값으로 사용되는 컬럼 하나면  @Column  을 사용해서 컬럼명 지정 가능
  •  @CollectionTable  생략시 기본값으로 매핑됨
    • default: {엔티티이름}_{컬렉션 속성 이름}

 

값 타입 컬렉션 사용

  • 값 타입 저장
Member member = new Member();

// 임베디드 값 타입
member.setHomeAddress(new Address("homeCity", "street", "1000"));

// 기본값 타입 컬렉션
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");

// 임베디드 값 타입 컬렉션
member.getAddressHistory().add(new Address("old1", "street1", "1000"));
member.getAddressHistory().add(new Address("old2", "street2", "2000"));

em.persist(member);
  • member 엔티티 영속화할 때 member 엔티티의 값 타입들 함께 저장됨
    • member: INSERT 쿼리 1번
    • member.setHomeAddress: 임베디드 값 타입이라 member 저장 쿼리에 포함
    • member.getFavoriteFoods: INSERT 쿼리 3번
    • member.getAddressHistory: INSERT 쿼리 2번
    • 총 INSERT 6번 실행됨

 

  • 값 타입 조회
em.flush();
em.clear(); // 위에서 타입 저장값 클리어하고 시작

Member findMember = em.find(Member.class, member.getId());
  • 값 타입 컬렉션도 조회할 때 fetch 전략 선택 가능( LAZY → default )
    •  @ElementColection(fetch = FEtchType.LAZY) 
    • member: 회원만 조회함. SELECT 쿼리 1번
    • member.setHomeAddress: member 조회할 때 같이 조회됨
    • member.getFavoriteFoods: LAZY로 설정되어 실제 컬렉션 사용시 SELECT 쿼리 1번 호출
    • member.getAddressHistory: LAZY로 설정되어 실제 컬렉션 사용시 SELECT 쿼리 1번 호출

 

  • 값 타입 수정
Member member = em.find(Member.class, member.getId());

// 임베디드 값 타입 수정
// homecity -> newcity
Address a = findMember.getHomeAddress();
Member.setHomeAddress(new Address("newcity", a.getStreet(), a.getZipcode());

// 기본값 타입 컬렉션 수정
// 치킨 -> 한식
member.getFavoriteFoods().remove("치킨");
member.getFavoriteFoods().add("한식");

// 임베디드 값 타입 컬렉션 수정
member.getAddressHistory().remove(new Address("old1", "street1", "1000"));
member.getAddressHistory().add(new Address("old3", "street3", "3000"));
  • 임베디드 값 타입 수정: HomeAddress 임베디드 값 타입은 member 테이블과 매핑되었으므로 member 테이블만 UPDATE 쿼리 수행
  • 기본값 타입 컬렉션 수정: 자바의 String 타입은 수정 불가이므로 제거하고 새로 추가해야 함
  • 임베디드 값 타입 컬렉션 수정: 값 타입은 불변해야 하므로 기존거를 삭제하고 새로 등록

 

값 타입 컬렉션의 제약사항

  • 특정 엔티티 하나에 소속된 값 타입은 변경되도 자신이 속한 텐티티를 DB에서 찾아 값 바꾸면 된다.
    • 값 타입 컬렉션에 보관된 값 타입들의 경우에는 별도의 테이블에 보관되므로 변경시 원본 데이터 찾기 어렵다.
      • 따라서 JPA 구현체들은 값 타입 컬렉션에 변경 사항 발생하면 컬렉션이 매핑된 테이블의 연관된 모든 데이터 삭제 후 현재 들어있는 값 새로 DB에 저장한다.
  • 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼 묶어서 pk 구성해야 한다.
    • pk 제약조건으로 nullable 하게 사용 못하고 같은 값 중복 저장도 안된다.
  • 위 문제들 해결하려면 값 타입 컬렉션 쓰는 대신 새 엔티티 만들어서 1:N 관계로 사용하면 된다.
    • 영속성 전이 + 고아 객체 제거 기능 적용하면 값 타입 컬렉션처럼 사용할 수 있다.

'Develop > JPA' 카테고리의 다른 글

[JPA/기본편] 객체지향 쿼리  (0) 2024.03.20
[JPA/기본편] 프록시  (0) 2024.03.20
[JPA/기본편] 상속 관계 매핑  (0) 2024.03.20
[JPA/기본편] 연관관계 매핑  (0) 2024.03.20
[JPA/기본편] 연관관계  (1) 2024.03.20

댓글