Post

[JPA] 값 타입

값 타입

엔티티 타입


  • @Entity로 정의합니다.
  • 데이터가 변해도 식별자로 지속해서 추적 가능.

값 타입 분류


기본값 타입

  • 자바 기본 타입(int, double)
  • 래퍼 클래스(Integer, Long)
  • String

임베디드 타입(embedded type, 복합 값 타입)

컬렉션 값 타입(collection value type)

기본값 타입


  • int, long, String 같은 단순히 값으로 사용하는 자바 기본 타입
  • 식별자가 없으므로 값 변경시 추적 불가
  • 생명주기가 엔티티에 의존
  • 값 타입은 공유하면 안 된다.(사이드 이펙트 x)
  • 값 복사(call by value)

임베디드 타입


  • 새로운 값 타입을 사용자 정의로 생성 가능.
  • Member 타입처럼 주로 기본값 타입을 모아서 만든 복합 타입
  • int, String과 같은 값 타입(추적 안 돼서 변경하면 끝)
  • 여러 엔티티에서 공유하면 위험함!! 왜냐하면 임베디드 값이 바뀌면 종속하는 모든 엔티티의 값이 바뀌기 때문! 따라서 같은 값을 넣고 싶으면 새로운 임베디드 값을 만들고 밸류 복사하는게 좋다!

임베디드 타입 사용법


  • @Embeddable: 값 타입을 정의한느 곳에 표시
  • @Embedded: 값 타입을 사용하는 곳에 표시
  • 기본 생성자는 필수!

임베디드 타입의 장점


  • 재사용 가능!
  • 높은 응집도 구현
  • Period.isWork()처럼 해당 값 타입만 사용하는 의미 있는 메소드 만들 수 있음
  • 임베디드가 소유되어 있는 엔티티에 생명주기를 의존함

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


  • 임베디드 타입도 결국 엔티티의 값일 뿐이다
  • 임베디드 타입을 사용하기 전과 후의 매핑하는 테이블은 같다
  • 세밀하게 엔티티 매핑하는 것이 가능
  • 잘 설계한 orm 어플은 매핑한 테이블보다 클래스의 수가 더 많

@AttributeOverride: 속성 재정의


  • 한 엔티티에서 같은 값 타입을 사용하기 위해 사용
  • 컬럼 명이 중복 됨

불변 객체


  • 객체 타입의 사고를 막으려면 원천 차단해야함.
  • 생성자로만 값을 설정하고 Setter를 만들지 않으면 됨.

값 타입 비교


  • 동일성 비교: 인스턴스의 참조 값을 비교(==)
  • 동등성 비교: 인스턴스 값 비교 (equals())
  • 그래서 되도록 equals()를 쓰고 이것을 재정의 하는 것이 좋다.(이때 getter를 쓰는게 좋다. 왜냐하면 그냥 필드로 접근하면 프록시 객체일 때 접근을 못하기 때문)
1
2
3
4
5
6
7
8
9
10
11
12
@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return Objects.equals(getCity(), address.getCity()) && Objects.equals(getStreet(), address.getStreet()) && Objects.equals(getZipcode(), address.getZipcode());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getCity(), getStreet(), getZipcode());
    }

값 타입 컬렉션


  • 값 타입을 하나 이상 저장할 때 사용한다.
  • @ElementCollection, @CollectionTable 어노테이션 사용한다.
  • DB는 컬렉션을 같은 테이블에 저장할 수 없다.
  • 따라서 별도의 테이블 필요함.
  • 컬렉션은 정의된 엔티티에 의해서 라이프 사이클이 정해진다.
  • 따라서 영속성 전이(cascade) + 고아 객체 제거 기능을 같이 있는 거라고 보면 된다.
  • 사용할 때는 지연로딩이라 그 컬렉션을 조회 등, 사용할 때 엔티티와 연결한다.
  • 수정할 때는 새로운 객체로 갈아 끼워야 한다!
1
2
3
4
5
6
Member findMember = em.find(Member.class, member.getId());
Address address = findMember.getAddress();
findMember.setAddress(new Address("newCity",address.getStreet(), address.getZipcode()));

---
findMember.getAddress().setCity("newCity") <- 이거는 사용하면  된다!

값 타입 컬렉션의 제약사항


  • 엔티티와 다르게 식별자 개념이 없어서 값을 변경하면 추적이 어렵다
  • 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//주소이력 바꾸기 equals와 hashCode를 재정의 했기 때문에 new 해도 비교가 가능!
findMember.getAddressHistory().remove(new Address("old1","street","1000"));
findMember.getAddressHistory().add(new Address("newCity1","street","1000"));

---
//한개 삭제하고 한개 저장했는데 다 삭제하고 다시 2개를 넣었다.

Hibernate:
    /* delete collection hellojpa.Member.addressHistory */ delete
        from
            ADDRESS
        where
            MEMBER_ID=?
Hibernate:
    /* insert collection
        row hellojpa.Member.addressHistory */ insert
        into
            ADDRESS
            (MEMBER_ID, city, street, zipcode)
        values
            (?, ?, ?, ?)
Hibernate:
    /* insert collection
        row hellojpa.Member.addressHistory */ insert
        into
            ADDRESS
            (MEMBER_ID, city, street, zipcode)
        values
            (?, ?, ?, ?)
  • 값 타입 컬렉션의 기본키는 모든 컬럼 묶어서 구성: null 안되고, 중복 x

값 타입 컬렉션 대안


  • 그냥 실무에서는 일대다 관계를 고려

AddressEntity.java

1
2
3
4
5
6
7
8
@Id
    @GeneratedValue
    private Long id;
    private Address address;

    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

Member.java

1
2
@OneToMany(mappedBy = "member",cascade = CascadeType.ALL, orphanRemoval = true)
private List<AddressEntity> addressEntityList = new ArrayList<>();

**main.java**

1
2
3
4
5
6
7
8
9
10
AddressEntity addressEntity1= new AddressEntity("old1","street","10000");
            AddressEntity addressEntity2= new AddressEntity("old2","street","10000");

            addressEntity1.setMember(member);
            addressEntity2.setMember(member);

            member.getAddressEntityList().add(addressEntity1);
            member.getAddressEntityList().add(addressEntity2);

            em.persist(member);
This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.