Chapter4. Repository와 Model 구현

김신영·2023년 12월 7일
0

DDD

목록 보기
4/9
post-thumbnail

JPA를 이용한 Repository 구현

모듈 위치

repository-module.png

Repository 기본 기능 구현

  • ID로 Aggregate 조회하기
  • Aggregate 저장하기
public interface OrderRepository {
    Optional<Order> findById(OrderNo id);
    void save(Order order);
}

Mapping 구현

erd.png

Entity와 Value 매핑 : @Entity, @Embeddable, @Embedded

  • Aggregate Root는 Entity이므로, @Entity로 매핑한다.
  • Value는 @Embeddable로 매핑한다.
  • Value 타입의 프로퍼티는 @Embedded로 매핑한다.
import com.myshop.order.command.domain.Receiver;
import com.myshop.order.command.domain.ShippingInfo;
import javax.persistence.Embedded;

@Entity
@Table(name = "ORDERS")
public class Order {
    // ...

    @Embedded
    private Orderer orderer;

    @Embedded
    private ShippingInfo shippingInfo;
}
@Embeddable
public class Orderer {
    
    @Embedded
    @AttributeOverrides(
        @AttributeOverride(name = "id", column = @Column(name = "ORDERER_ID"))
    )
    private MemberId memberId;

    @Column(name = "ORDERER_NAME")
    private String name;
}
@Embeddable
public class MemberId implements Serializable {

    @Column(name = "MEMBER_ID")
    private String id;
}
@Embeddable
public class ShippingInfo {

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "zipCode", column = @Column(name = "SHIPPING_ZIP_CODE")),
        @AttributeOverride(name = "address1", column = @Column(name = "SHIPPING_ADDRESS1")),
        @AttributeOverride(name = "address2", column = @Column(name = "SHIPPING_ADDRESS2"))
    })
    private Address address;

    @Column(name = "SHIPPING_MESSAGE")
    private String message;
    
    @Embedded
    private Receiver receiver;
}

JPA를 위한 기본 생성자

  • JPA에서 @Entity@Embeddable로 Class를 매핑하려면, 기본 생성자가 있어야 한다.
  • 따라서 가급적이면 protected 혹은 패키지 범위로 기본 생성자를 선언하는 것을 추천한다.
import javax.persistence.Column;

@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Receiver {

    @Column(name = "RECEIVER_NAME")
    private String name;

    @Column(name = "RECEIVER_PHONE")
    private String phone;
    
    public Receiver(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }
}

AttributeConverter를 이용한 Value 매핑 처리 : @Convert, @Converter

  • AttributeConverter를 구현하고, @Convert, @Converter를 이용해서 Value 타입을 매핑한다.
  • @Converter(autoApply = true)를 이용하면, 모든 Entity에 대해 자동으로 매핑한다.
public interface AttributeConverter<X, Y> {
    Y convertToDatabaseColumn(X var1);

    X convertToEntityAttribute(Y var1);
}

Value Collection : 별도 테이블로 매핑

  • Value Collection을 별도의 테이블로 매핑하려면, @ElementCollection@CollectionTable을 이용한다.
import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.OrderColumn;

@Entity
@Table(name = "ORDERS")
public class Order {

    @EmbeddedId
    private OrderNo number;

    @ElementCollection
    @CollectionTable(name = "ORDER_LINES", joinColumns = @JoinColumn(name = "ORDER_NUMBER"))
    @OrderColumn(name = "LINE_IDX")
    private List<OrderLine> orderLines;
    
    // ...
}
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;

@Embeddable
public class OrderLine {

    @Embedded
    private ProductId productId;

    @Column(name = "PRICE")
    private Money price;
    
    @Column(name = "QUANTITY")
    private int quantity;
    
    @Column(name = "AMOUNTS")
    private Money amounts;
    
    // ...
}

@ElementCollection

public @interface ElementCollection {
    Class targetClass() default void.class;

    FetchType fetch() default FetchType.LAZY;
}

@CollectionTable

public @interface CollectionTable {
    String name() default "";

    String catalog() default "";

    String schema() default "";

    JoinColumn[] joinColumns() default {};

    ForeignKey foreignKey() default @ForeignKey(ConstraintMode.PROVIDER_DEFAULT);

    UniqueConstraint[] uniqueConstraints() default {};

    Index[] indexes() default {};
}

Value Collection : 한 개 칼럼으로 매핑

  • Value Collection을 Wrapping하는 Value 클래스를 선언하고,
    • AttributeConverter를 활용하면, Value Collection을 한 개 칼럼으로 매핑할 수 있다.

Value를 이용한 ID 매핑 : @EmbeddedId, @Embeddable

  • 식별자 자체를 Value 타입으로 만들수 있다.
  • @EmbeddedId@Embeddable을 이용해서 식별자를 Value 타입으로 매핑한다.
  • 식별자로 사용할 Value 타입은 다음과 같은 조건을 만족해야 한다.
    • Serializable을 구현해야 한다.
    • equals()hashCode()를 구현해야 한다.
import javax.persistence.Table;

@Entity
@Table(name = "ORDERS")
public class Order {

    @EmbeddedId
    private OrderNo number;

    // ...
}
@Embeddable
public class OrderNo implements Serializable {

    @Column(name = "ORDER_NUMBER")
    private String number;
    
    private boolean is2ndGeneration() {
        return number.startsWith("N");
    }
}

Value Collection을 @Entity로 매핑

  • 다음과 같은 기능을 사용하여, Value Collection을 @Entity로 매핑할 수 있다.
    • @Inheritance
    • @DiscriminatorColumn
    • @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true)

단점

  • Value Collection의 clear 메서드를 호출하면, 모든 데이터를 select 쿼리로 조회한 뒤, 각 개별 Entity에 대해 delete 쿼리를 실행한다.
    • 반면, @Embeddable을 통해서 매핑한다면, 단순히 delete 쿼리만 실행한다.

ID 참조와 Join 테이블을 이용한 단방향 M-N 매핑

import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Table;

@Entity
@Table(name = "PRODUCT")
public class Product {

  @EmbeddedId
  private ProductId id;

  @ElementCollection
  @CollectionTable(
      name = "PRODUCT_CATEGORY", 
      joinColumns = @JoinColumn(name = "PRODUCT_ID")
  )
  private Set<CategoryId> cartegoryIds;
}

Aggregate 로딩 전략

애그리거트에 맞게 즉시 로딩과 지연 로딩을 적절하게 선택해야한다.

즉시 로딩

  • 애그리거트를 완전한 상태로 유지할 수 있다.
  • 하지만 로딩시 불필요하게 많은 데이터가 로딩된다.
  • 실행 빈도, 트래픽, 지연 로딩 시 실행 속도 등의 성능을 검토해야한다.

지연 로딩

  • JPA에서 지원하는 지연 로딩으로 상태를 변경하는 시점에 지연 로딩 가능하다.

Aggregate 영속성 전파

저장 메서드는 애그리거트 루트만 저장하면 안되고, 애그리거트에 속한 모든 객체를 저장해야 한다.

삭제 메서드는 애그리거트 루트뿐만 아니라 애그리거트에 속한 모든 객체를 삭제해야 한다.

@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true)
  • 영속성 전이
  • 고아 객체

도메인 구현과 DIP

DIP.png

profile
Hello velog!

0개의 댓글