김찬진의 개발 블로그

23/12/28 [JPA 값 타입 : Collection type + 총정리] 본문

1일1배움/JPA (김영한 님)

23/12/28 [JPA 값 타입 : Collection type + 총정리]

kim chan jin 2023. 12. 28. 21:44

JPA 데이터 타입

 

 

Collection Type

값 타입 중 Collection type 을 쓸 때

일대다 관계로 풀 수 밖에 없다

컬렉션에 값들이 들어갈텐데

이 여러 값들을 한번에 DB에 저장할 수 없기 때문이다

 

예를 들어 

Member 엔티티가 

좋아하는 음식을 담은 Set<String> favoriteFoods 

과거에 방문했던 주소들을 담은 List<Address> addressHistory

라는 컬렉션 타입을 갖는다고 가정한다면 다음과 같이 연관관계 매핑이 만들어질 것이다

 

 

코드로 보면 다음과 같이 작성할 수 있다

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @ManyToOne // 엔티티 타입. @XXXToOne 은 기본 전략이 EAGER
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    @Column(name = "USERNAME") // 값타입 - 기본 값타입 - String 타입
    private String username;

    @Embedded // 값타입 - 임베디드 타입
    private Period workPeriod;

    @Embedded // 값타입 - 임베디드 타입
    private Address homeAddress;

    @ElementCollection // 값타입 - 컬렉션 타입
    @CollectionTable(name = "FAVORITE_FOOD", joinColumns = @JoinColumn(name = "MEMBER_ID"))
    @Column(name = "FOOD_NAME") // 컬렉션 타입의 요소의 컬럼명 지정
    private Set<String> favoriteFoods = new HashSet<>();

    @ElementCollection // 값 타입 컬렉션 타입
    @CollectionTable(name = "ADDRESS_HISTORY", joinColumns = @JoinColumn(name = "MEMBER_ID"))
    private List<Address> addressHistory = new ArrayList<>();

    public void changeTeam(Team team) { // 연관관계 편의 메서드
        this.team = team;
        team.getMembers().add(this);
    }
    
    // getter, setter 생략
}

@ElementCollection

    - 컬렉션 타입을 필드로 사용함을 알림

    - 컬렉션 타입은 일대다 관계로 풀 수 밖에 없음

@CollectionTable(name = "테이블명", joinColumns = @JoinColum(name = "컬럼명"))

    - 컬렉션 타입을 일대다관계로 풀어주는데 이때의 테이블명을 정함

    - 다테이블을 만들때 PK(FK)를 어떤 테이블의 어떤 컬럼명으로 가져와 사용할 것인지

@Column(name = "컬럼명")

    - 컬렉션 타입의 요소의 컬럼명을 지정함

    - 지정하지 않으면 테이블명과 동일하게 컬럼명이 정해짐

@Embeddable
public class Address {

    private String city;
    private String street;
    private String zipcode;
    
    // getter, setter 생략
}
@Embeddable
public class Period {

    private LocalDateTime startDate;
    private LocalDateTime endDate;
    
    // getter, setter 생략
}

 

 

값 타입 총정리

값 타입의 [영속성 전이와 고아 객체 제거] (default)  /  [즉시로딩 또는 지연로딩]

엔티티 타입의 [영속성 전이와 고아 객체 제거] (option)  /  [즉시로딩 또는 지연로딩]

 

결론부터 말하자면

 

값 타입은 [영속성 전이 + 고아 객체 제거] (default)

엔티티 타입은 [영속성 전이 + 고아 객체 제거] (option)

 

값 타입 중 기본 값 타입(primitive, Wrappyer, String), 임베디드 값 타입즉시 로딩

값 타입 중 컬렉션 타입지연 로딩

엔티티 타입은 @XXXToOne즉시 로딩

엔티티 타입 중 @XXXToMany 는 지연 로딩

 

다음 코드 JpaMain 를 실행해보면 다음과 같이 쿼리가 나가고 테이블이 만들어지고 값이 저장되는 것을 확인할 수 있다

이때

(엔티티 매니저 팩토리, 엔티티 매니저, 트랜잭션 획득하는 코드는 생략했다)

 

중요한 하게 봐야 할 것은

 

1. 값타입(기본값타입,임베디드타입,컬렉션타입) "영속화 전이" + "고아 객체 제거 기능"을 디폴트로 가진다는 것

2. 엔티티 타입 또한 "영속화 전이" + "고아 객체 제거 기능"을 설정할 수 있다는 것

이다.

 

1 을 확인하기 위해

username, address, favoriteFoods, addressHistory 를 영속화하는 것이 아니라

setter 를 이용한 것만으로도 괜찮다는 점을 잘 기억하자!

 

2 를 확인하기 위해

member 엔티티를 영속화하지 않고도

team 을 영속화했더니 

member 도 따라서 영속화된 점을 잘 기억하자!

 

 

출력은 다음과 같다

 

값타입은 다음과 같이 정리할 수 있다

기본 값 타입(값타입), 임베디드 타입(값타입) 기본전략은 즉시로딩

컬렉션 타입(값타입) 기본전략은 지연로딩

엔티티 타입 (@XXXToOne) 기본전략은 즉시로딩

엔티티 타입 (@XXXToMany) 기본전략은 지연로딩

 

생각해보면 당연하다

기본 값 타입 임베디드 타입는 엔티티 테이블에 박혀있으니깐 조회할 때 같이 조회해야 하고

컬렉션 타입(값타입)은 일대다로 테이블이 생성되어있으니깐 지연로딩해도 된다

 

값타입을 수정할 때에는 불변객체를 다루는 방식으로

 

 

값 타입은 엔티티와 다르게 식별자 개념이 없다

기본 값 타입, 임베디드 타입은 주인 엔티티에 박혀있을 뿐 자기 스스로 식별자를 갖지 못한다는 것

컬렉션 타입또한 주인 엔티티의 PK를 자신(컬렉션 타입)의 PK(FK)로 사용한다는 것을 보면 알 수 있다.

 

즉, 값 타입은 변경하면 추적이 어렵다는 것이다

그래서 기본적으로 컬렉션 타입을 변경하면(UPDATE)

주인 엔티티의 PK를 PK(FK)로 가진 컬렉션 타입의 모든 요소들을 DELETE 하고

다시 요소들을 INSERT 하는 것이다

 

모든 요소를 DELETE하고 남아있는 요소들을 INSERT 하는 방식은 너무 비효율적이다

그래서 이걸 보완할 방법이 있긴 한데 @OrderColum(name = "address_history_order") 처럼 컬렉션 타입에 PK를 부여하는 것이다.

하지만 이 방법은 쓰면 안된다. 왜냐하면 의도치 않은 값이 들어갈 위험이 크기 때문이다

 

아니면 컬렉션 타입으로 만들어진 다테이블의 모든 컬럼을 묶어서 PK로 만드는 방식도 있다.

하지만 이 방법도 좋지 않다. PK로 만들기 위해서는 null 을 허용할 수 없고, 중복된 값을 저장할 수 없다는 제약 조건이 있기 때문이다

 

그래서

컬렉션 타입 List<Address> addressHistory 을 쓰는 것 대신

값 타입 Address 을 필드로 가지는 새로운 엔티티 AddressEntity 를 만들고

Member 엔티티와 AddressEntity 엔티티를 일대다 연관관계 매핑을 하는 것이 낫다

값 타입을 흉내내기 위해 영속화 전이, 고아 객체 제거 기능도 설정해주면 좋다

 

다음과 같이 코드를 작성 또는 수정할 수 있다.

 

 

 

Comments