Lee's Grow up

[JAVA/JPA] 값 타입 본문

PROGRAMMING/JAVA

[JAVA/JPA] 값 타입

효기로그 2020. 4. 13. 23:11
반응형

해당 내용은 인프런의 자바 ORM 표준 JPA 프로그래밍 - 기본편, 김영한 의 내용을 기반으로 정리해서 작성한 글입니다.
자세한 내용은 해당 강의 또는 책을 구매하시는걸 추천합니다.


JAP는 크게 값 타입을 2종류로 분류한다.

  1. 엔티티 타입 -> Entity로 정의된 객체 , 데이터가 변해도 식별자로 지속해서 추적 가능
  2. 값 타입 -> int, Integer처럼 단순히 값으로 사용되는 기본 자료형 또는 래퍼클래스 또는 String

값 타입 분류

  1. 기본 값 타입
    1. 자바 기본 타입 int float
    2. 래퍼 클래스 Integer Double Float
    3. String
  2. 임베디드 타입
    1. 좌표를 묶어서 쓰고 싶은 값 ex)포지션 클래스 -> 포지션 클래스 자체를 값 처럼 쓰는걸 임데디드 타입
  3. 컬렉션 타입 -> java 컬렉션
기본값 타입
  • String name, int age
  • 생명주기를 엔티티의 의존
  • 값 타입은 공유 x ex) int a = 10 ; int b = a; 일때 a = 30 으로 변경 해도 b는 10 그대로
  • 래퍼 클래스는 공유는 가능하나, 기본적으로 java에서는 값을 변화시킬 수가 없기 때문에 변경이 불가능함 사이드 이펙트 발생 x
임베디드 타입 ( 복합 값 타입 )
  • 새로운 값 타입을 정의할 수 있음 이를 JPA는 임베디드 타입이라고 함
  • 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 함
  • int, String과 같은 값 타입의 모음

예제로 멤버는 이름 아이디 근무시간 (startDate, endDate)를 묶고 주소 (address, zipCode, city ) 등을 묶은 임베디드 타입을 만들 수 있다

  • 장점 : 재사용, 높은 응집도, Period.isWokr() 처럼 해당 값 타입만 사용하는 의미 있는 메소드를 만들 수 있음

임베디드 타입을 포함한 모든 값 타입은, 값 타임을 소유한 엔티티에 생명주기를 의존함, 기본 생성자가 필수

@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String name

    // 기간 
    @Embedded // 해당 어노테이션은 생략 가능하나,유지보수 측면에서 사용을 권장 해당 클래스만 보고 임베디드 타입인지 알 수 있음
    private Period workPeriod;

    // 주소
    @Embedded
    private Address homeAddress
}

// 값 타입은 @Embeddable를 선언
@Embeddable
public clas Period{

    private LocalDateTime startDate;
    private LocalDateTime endTime;

    public void getWorkTime() {  
        ... ( 근무 시간과 관련된 로직 ) 
    } 
}

위 방식처럼 좀 더 entity를 객체스럽게 테이블 구조와 다르게 역할에 따라 분리하고, 하나의 테이블로 사용 가능
엔티티와 값 타입임을 명시해야 하며 둘 중 한곳에만 해도 되지만 유지보수성 을 위해 둘다 사용을 권장

공통으로 사용 가능

Entity와 마찬가지로 @Column등 사용가능하고, 값 으로 Entity도 가질 수 있다

동일한 Embeded 객체 사용

값으로 동일한 객체를 쓰고 싶은 경우 @AttributeOverride 속성 재정의해서 사용 가능, 필요하면 참고할것

@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    @Embedded
    private Address homeAddress // Address 객체 중복

    @Embedded
    private Address workAddress // Address 객체 중복
}

위와 같을 경우 Address를 두번 사용하기 때문에 오류 발생 아래와 같이 새로 매핑을 해줘야 사용 가능

@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String name

    // 기간 
    @Embedded // 둘중 하나만 써도 되나 유지보수 측면에서 사용을 권장
    private Period workPeriod;

    // 주소
    @Embedded
    private Address homeAddress

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="city", column=@Column(name = "work_city")), // 이런식으로
    })
    private Address workAddress;
}
정리
  • 임베디드 타입은 엔티티의 값일 뿐이다.
  • 임베디드 타입을 사용해도 매핑되는 테이블은 변경이 없다. 그냥 추출일 뿐
  • 공통되는 로직을 재사용할 수 있게 해준다. ex) 좌표 계산이나, 주소를 리턴해주는 로직 등

값 타입과 불변 객체

값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념이다. 따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다.

  • 값 타입 공유 참조
    • 임베디드 타입 같은 값 타입은 여러 엔티티에서 공유하면 위험함, 즉 객체이기 때문에 참조 값으로 여러개가 묶일 수 있다. 이럴 경우 참조된 모든 객체에 영향을 주기 때문에 위험
  • 값 타입을 불변 객체로 만든다. String, Integer 처럼...
    • setter을 제거하고 getter만 만들어둔다.
    • builder 패턴을 통해 객체 생성

값 타입은 동등성 동일성 비교 인스턴스 참조 값 비교 동일성 값 자체 비교는 동등성 -> equals,hash를 모든 필드에 대해서 재정의 해야 함

값 타입 컬렉션

값 타입 자체를 컬렉션에 담아서 사용하는 경우를 말함

@Embedded
private Address homeAdderss;

@ElementCollection
@CollectionTable( name = "addres_history" , joinColumns = @JoinColumn(name = "member_id"))
private List<address> addressHistory ; // 와 같은 형식

@ElementCollection
@CollectionTable( name = "favorite_food" , joinColumns = @JoinColumn(name = "member_id"))
@Column(name = "food_name")
private List<String> favoriteFood ; // 와 같은 형식

이때 테이블은 1:N 이기 때문에 객체에서는 한번에 표현 가능하지만 테이블은 별도의 테이블로 생성이 된다.
이때 favorite_food 같은 경우 필드가 하나일때그냥 @Column 선언가능

이력 테이블용으로 사용이 용이, 기본적으로 지연 로딩, member만 persist해도 전부다 값이 들어감.
왜냐 값타입이기 때문 그냥 Member의 컬럼 취급을 하기 때문

컬렉션도 값타입과 마찬가지로 불변객체여야 한다 side effective가 발생할 확률이 높음
객체를 새로 복사해서 통으로 값을 복사&변경 하던가, 별도의 메소드를 통해서 속성에 접근하는 방식을 구현할 것

값 타입의 컬렉션 변경
  • 값 타입은 엔티티와 다르게 식별자 개념이 없다. 그래서 값은 변경하면 추적이 어렵다
  • 값 타입 컬렉션에 변경 사항이 발생하면 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
  • 값 타입 컬렉션을 매핑하는 테이블은 키값이 없기 때뭉네, 모든 컬럼을 묶어서 기본 키를 구성해야 함 -> null 입력 x, 중복 저장 x

위와 같이 문제 때문에 관계가 복잡해질 경우, 값 타입 컬렉션 대신 일대다 관계를 고려할 것 ( 엔티티로 값 타입을 빼기 )

@Entity
public class AddressEntity {

    @Id
    Long id

    private Address address;
}

로 변경 후

// Member entity
@OneToMany ( casecade = CascadeType.ALL , orphanRemoval = true)  
@JoinColumn( name = "member_id")
private List<AddressEntity> addressHistory = new ArrayList<>();
// 위와 같이 사용  실무에서 더 사용이 용이함

casecade = CascadeType.ALL 속성의 경우 Member을 저장할 때 AddressEntity 도 같이 저장하는 전파 속성
orphanRemoval = true 는 연관관계가 끊긴 엔티티를 테이블에서도 자동으로 삭제쿼리를 실행하는 속성

결론 : 값 타입 컬렉션 대신 엔티티로 승격해서 사용해라. 엔티티로 만들어야 할지 값 타입인지 잘 구분할것, 식별자가 필요한지 우뮤, 사용할 경우 진짜 단순하게 셀렉트 박스에 치킨, 피자를 멀티로 선택가능한 셀렉트 박스일 경우, 이런 단순한 경우에만 사용, 추적할 필요도 없고 값이 바뀌어도 업데이트 칠 필요가 없는 경우
반응형
Comments