JPA - 값 타입(value type)

YeongUng Kim·2021년 6월 30일
0

JPA

목록 보기
4/4
post-thumbnail

JPA Data Type

Entity Type

  • @Entity 어노테이션으로 표현
  • 엔티티의 필드 값이 변경되도 식별자로 구분이 가능함 , 추적 가능

Value Type

  • 자바의 기본타입(int,long,char 등등),Object로 표현, 단순한 값
  • 값이 변경되면 추적이 불가능

JPA 값 타입 분류

  • Java primitive type(int,long)
  • wrapper class(Integer,Long)
  • embedded type
  • collection value type

Primitive type(기본 값 타입)

우리가 자주쓰는 int , char , short , long 등 자바 원시 데이터 타입이다. 생명주기를 엔티티에 의존하며 엔티티가 삭제되면 (당연히) 값 타입도 같이 삭제 된다. 값 타입은 엔티티간 공유가 불가능한 특성이 있다.자바 원시타입은 항상 값을 복사해서 사용하기 때문.

Wrapper class

wrapper class는 원시타입이 아닌 오브젝트 이지만 공유는 가능할지언정 변경 불가능으로 고정되어있다.

Embedded type

새로운 값 타입을 직접 정의한다. 이를 JPA에서 embedded type이라 한다. 주로 기본 값 타입의 집합으로 표현된다. 엔티티가 아닌 기본 값 타입처럼 추적 불가능한 타입이다.

사람이라는 엔티티는 이름,나이,성별,집주소,직장주소 라는 필드를 가진다. 이처럼 한번에 묶어서 표현할 수 있는 값 타입이다.물론 다른 클래스를 레퍼런스로 가져와 필드로 사용가능하다.

@Embeddable

  • 값 타입을 정의하는 곳에 표시

@Embedded

  • 값 타입을 사용하는 곳에 표시

Embedded Type 장점

  • 재사용이 가능
  • 높은 응집도를 가짐
  • 값 타입에 특정 필드만 사용하는( e.g. Period.isWork() ) 의미있는 메소드 사용 가능
  • 생명주기를 엔티티에 의존한다. 엔티티가 삭제되면 해당 값 타입도 같이 소멸한다.

Embedded Type 과 테이블 맵핑

  • 임베디드 타입은 엔티티의 값 그 이상도 이하도 아님
  • 임베디드 타입의 사용 전과 후에도 맵핑되는 테이블은 같다
  • 오브젝트 - 테이블을 세밀하게 맵핑하는것이 가능하다 필드 클래스에서 특정 필드를 조작할 수 있는 메소드를 만들어 놓고 요긴하게 사용 가능하다.
  • 잘 설계된 ORM - 클래스 갯수 > 테이블 갯수

@AttributeOverride - 속성 재정의

만약 엔티티 안에서 같은 값 타입을 사용한다고 가정해보자, 당연히 컬럼명이 중복될것이다. Person 엔티티 안에 집주소와 직장주소 두개를 사용해아 하는데, 같은 Address 타입을 사용하게 되면 당연히 에러가 난다. 이럴때는
@AttributeOverrides
@AttributeOverride
를 사용하여 컬럼명을 명시해주면 된다.

값 타입과 불변 객체

값 타입은 단순,안전하게 핸들링 할 수 있어야 한다. 그렇지 않은 예를 들어보자.

Address address = new Address("서울","도산대로","22"); // city,street,zipcode

Member member1 = new Member();
member1.setName("m1");
member1.setAddress(address);

Member member2 = new Member();
member2.setName("m2");
member2.setAddress(address);

member1.getAddress().setName("부산") // address city 변경

의도는 member1의 도시명만 바꾸고 싶었는데 둘다 변경됐다. 이러한 사이드 이펙트에 의한 버그는 추적하기도 힘들다. embedded type을 여러 엔티티에서 공유할경우 이러한 부작용이 발생한다.

Address newaddress = new Address(address.getCity(),address.getStreet(),address.getZipCode());

로 값을 복사해서 사용해야한다. 당연한 얘기다..실제 인스턴스를 그대로 사용하면 위험하다.
하지만 이러한 실수를 막을 방법이 없다. 누군가 실수로 address를 그냥 써버리면 아무런 문제없이 코드가 실행되기 때문이다. 오브젝트는 참조를 사용하여 문법상 문제가 없으니까. 이러한 경우는 타입을 수정할 수 없도록 불변 객체로 만들어 주는것이 키포인트.

값 타입 비교

int a = 10;
int b = 10;
System.out.print(a == b);

결과는 true

Address address = new Address("서울","도산대로",22);
Address newaddress = new Address("서울","도산대로",22);
System.out.print(address == newaddress);

결과는 false

당연하다. 오브젝트는 필드 값이 아닌 주소값으로 서로를 비교하기 때문.
String 에서 == 을 쓰지않고 equals 메소드를 쓰는것도 같은이치다.
이처럼 값 타입의 동등성을 비교할때에는 equals 메소드를 오버라이드하여 사용해야한다.

값 타입 컬렉션

자바의 Collection을 사용하여 값 타입의 정의하는 형식이다. RDB테이블에서는 Collection의 List,Set을 지원하지 않는다. 그래서 JPA에서는 이 값 타입 컬렉션을 이용하여 엔티티와 테이블을 맵핑한다.기본적으로 Collection에 있는 모든 인터페이스를 지원한다.

  • 값 타입을 하나 이상 저장할 때 사용
  • @ElementCollection , @ColellectionTable 어노테이션 사용
@ElementCollection
@CollectionTable(name = "FOOD",joinColumns = 	@JoinColumn(name = "MEMBER_ID"))
private Set<String> favorite = new HashSet<>();

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

이와같이 어노테이션을 사용하여 컬렉션 값 타입을 선언한다. joinColumn 은 Member의 PK인 MEMBER_ID를 사용한다. 값 타입을 선언하면 1:N 처럼 컬렉션을 저장하는 별도의 테이블이 맵핑된다.Member persist시 food 테이블도 같이 persist 된다. 값 타입 컬렉션은 라이프 사이클이 Member에 소속되기 때문이다. 값 타입 컬렉션은 영속성 전이(Cascade) , 고아객체 제거 기능이 있다고 볼 수 있다.실제로 flush후 member 조회시 member만 조회한다. food는 지연로딩된다.

값 타입 컬렉션 제약사항

  • 값 타입은 엔티티와 다르게 식별자 개념이 없다
  • 값은 변경하면 추적이 어렵다
  • 값 타입 컬렉션에 변경 사항이 발생하면,주인 엔티티와 관련된 모든 컬렉션 데이터를 삭제하고, 컬렉션에 최종적으로 있는 모든 값을 다시 저장한다.
  • 값 타입 컬렉션이 맵핑된 테이블은 모든 컬럼(필드)를 묶어서 PK를 구성해야함 ( null X , 중복 저장 X)

이처럼 맵핑된 컬렉션이 몇개 없으면 괜찮지만 갯수가 많아지면, 성능상의 문제점이 있다. 결론은 웬만하면 쓰지말자

대안

  • 값 타입 컬렉션에 1:N 관계를 고려할것
  • 영속성 전이(Cascade) + 고아객체 제거를 사용하여 값 타입 컬렉션처럼 사용
@Entity
@Data
@Table(name="ADDRESS")
public class AddressEntitiy {

    @Id
    private Long id;

    private Address address;
}

@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntitiy> address = new ArrayList<>();

이처럼 자체적인 AddressEntity를 따로 만들고 1:N를 적용하여 값 타입을 엔티티로 승격하여 표현하는게 바람직하다 명확하게 값 타입이라고 판단 될때만 값 타입을 사용해야 한다.


출처:
자바 ORM 표준 JPA 프로그래밍

profile
기록하지 않으면 까먹어서 만든 블로그..

0개의 댓글