make it simple
article thumbnail

 

@OneToMany를 사용하여 부모와 자식 엔티티를 연결해보자.

 

위 설계도를 기반으로 코드로 구현해보자. @Entity를 이용해 매핑으로 처리해보자.

import javax.persistence.*;
import java.util.Date;

@Entity
@Table(name = "image")
public class Image {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "image_id")
    private Long id;
    
    @Column(name = "image_path")
    private String path;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "upload_time")
    private Date uploadTime;
    
    protected Image() {}
    
    public Image(String path) {
    	this.path = path;
        this.uploadtime = new Date();
    }
    
    protected String getPath(){
        return path;
    }
}
@Entity
@Table(name="product")
public class Product {
    @EmbeddedId
    private productId id;
    private String name;
    
    @Convert(converter = MoneyConverter.class)
    private Money price;
    private String detail;
    
    @OneToMany(
    	cascade = {CascadeType.PERSIST, CascadeType.REMOVE},
        orphanRemoval = true)
    @JoinColumn(name = "product_id")
    @OrderColumn(name = "list_idx")
    private List<Image> images = new ArrayList<>();
    
    
    public void changeImages(List<Image> newImages){
    	images.clear();
        images.addAll(newImages);
    }
}

 

  • Product Entity가 부모 객체이고 Image Entity가 자손 객체이다.
  • Product Entity에서 @OneToMany를 이용해서 Image Entity와 매핑 처리를 한다. 이로써, Image는 독자적인 생명 주기를 갖지 않고 부모 객체인 Product에 완전히 의존한다. Product가 저장될때 Image도 같이 저장되고 삭제될때도 같이 삭제되게 cascadeType 속성을 주었다.  orphanRemoval = true 속성도 줘야 삭제될 때 같이 삭제된다.
  • Product의 Image를 수정할때  changeImages함수 사용한다. clear()라는 메서드로 삭제해주고 새로 등록할 이미지를 addAll()로 넣어 주면 부모 객체가 수정될때 자식 객체도 수정된다.

casecade 속성이란?(출저: https://sin0824.tistory.com/16)

  • 부모 엔티티가 영속화될 때 자식 엔티티도 같이 영속화되고, 부모 엔티티가 삭제될 때 자식 엔티티도 삭제되는 등 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 전이되는 것을 의미

CascadeType(출저: https://sin0824.tistory.com/16)

  1. ALL → 모든 Cascade 적용
  2. PERSIST → 엔티티를 영속화할 때, 연관된 엔티티도 함께 유지
  3. MERGE → 엔티티 상태를 병합(Merge)할 때, 연관된 엔티티도 모두 병합
  4. REMOVE → 엔티티를 제거할 때, 연관된 엔티티도 모두 제거
  5. REFRESH → 상위 엔티티를 새로고침(Refresh)할 때, 연관된 엔티티도 모두 새로고침
  6. DETACH → 부모 엔티티를 detach() 수행하면, 연관 엔티티도 detach()

애그리거트 로딩 전략

  • JPA 매핑을 설정할 때 항상 기억해야 할 점은 애그리거트에 속한 객체가 모두 모여야 완전한 하나가 된다. 예를 들어 밑과 같이 루트 엔티티를 로딩하면 루트에 속한 모든 객체가 완전한 상태여야 된다.
Product product = productRepository.findById(id);
    @OneToMany(
    	cascade = {CascadeType.PERSIST, CascadeType.REMOVE},
        orphanRemoval = true, fetch = FetchType.EAGER)
    @JoinColumn(name = "product_id")
    @OrderColumn(name = "list_idx")
    private List<Image> images = new ArrayList<>();
    
    
    public void changeImages(List<Image> newImages){
    	images.clear();
        images.addAll(newImages);
    }
  • 위 처럼 FetchType.Eager 옵션은 즉시 로딩이라고 하기도한다. 이 옵션을 통해 애그리거트 루트가 로딩하는 시점에 애그리거트에 속한 모든 객체를 함께 로딩할 수 있는 장점이 있다. 하지만, 이 방법은 큰 단점이 있다. 자손 엔티티가 많을 수록 조회 성능 문제가 발생한다 -> 모든 조회 쿼리가 나간다. 모든 자손 객체를 사용하진 않는데 애그리거트 루트가 로딩되는 시점에 모든 자손 객체를 부르면 조회 성능 문제가 발생한다.
  • 애그리거트는 개념적으로 하나여야 한다. 그래서 상태를 변경하는 시점에 필요한 구성요소만 로딩해도 문제가 되지 않는다.
  • FetchType.Lazy은 지연 로딩이라 하며 실무에서도 많이 사용한다. 루트 엔티티를 조회할 때 다 부르지 않고 필요할 때만 불러서 사용함으로써 조회 성능이 좋아진다.

식별자 생성 기능

  • id 필드 값위에 @GeneratedValue(strategy = GenerationType.IDENTITY) 옵션을 통해 DB 자동 증가 컬럼 식별자로 사용할 수 있다. 그러면 객체가 생성 될 때마다 primary key가 자동 증가된다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

결론

  • CH.4 1편과 2편을 통해 JPA 에 대해 많이 배우게 된 챕터인것 같다. 알기도한 어노테이션도 보였지만 몰랐던 어노테이션도 보였다. 보면서 항상 도메인 설계를 할 때 어떻게 매핑하고 어떤 도메인이 생명주기를 주도하고 가져가는지도 고민을 많이하고 설계해야겠다는 생각이 들었다.

reference

profile

make it simple

@keep it simple

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!