[Spring Boot2][2] 2. ドメイン解析設計


🏷 需要分析



1𗞚𗞚会員機能
  • 会員登録
  • 会員照会
  • 2▼▼▼商品機能
  • 商品登録
  • 商品修正
  • 商品照会
  • 3▼▼▼注文機能
  • 商品注文
  • オーダー履歴照会
  • 注文キャンセル
  • 4」その他の要求
  • 商品は在庫管理が必要です.
  • 商品の種類は図書、レコード、映画.
  • 商品別.
  • 商品の注文時に配送情報を入力できます.
  • 🏷 ドメインモデルとテーブルの設計


    勘定科目の勘定科目ドメインモデルと表設計



    1𗞚会員、注文、商品の関係
  • 会員様は多様な商品をご注文いただけます.
  • しかも1回の注文で複数の商品を選べるので、注文と商品は多対多の関係にあります.
  • ただし、このような多対多関係は、リレーショナル・データベースのみならず、エンティティでもあまり使用されない.
  • そのため、図のように受注商品のエンティティを追加し、多対多関係を一対多、多対一関係に分解する😉
  • 2」商品分類
  • 商品は図書、レコード、映画に分けられ、商品の共通属性を使用するため、継承構造として表現される.
  • ✔¥会員実体分析



    1”メンバー(メンバー)
  • 氏名とimbeddyタイプの住所と注文書リストを持つ.
  • 2朕注文(Order)
  • 1回の注文で複数の商品を注文できるため、注文と注文商品は一対多の関係にある.
  • 注文には、注文商品の会員と配送情報、注文日、注文状態があります.
    注文状態は列挙型を採用し、注文、キャンセルを表すことができる.
  • 3▼▼▼注文品(OrderItem)
  • 注文した商品情報と注文金額、注文数量情報を持っている.
  • 4”𔣤商品(Item)
  • 保有氏名、価格、在庫数
  • 商品の種類は図書、レコード、映画があり、それぞれの属性が若干異なる.
  • 納品
  • 注文時に配送情報が生成され、注文と配送は一対一の関係となる.
  • 6π𔣤カテゴリ
  • 商品と多対多の関係を築く
  • 親、子、親、子の種別を結ぶ.
  • 7朕住所(Address)
  • 値型
  • 会員や配送用
  • ✔¥会員表分析



    1️⃣ MEMBER
  • 会員エンティティのAddress imbeddyタイプ情報が直接会員表に入っている.
  • これはDELIVERY表でも同じです.
  • 2️⃣ ITEM
  • アルバム、図書、映画タイプを1つのテーブルに統合(シングルテーブル戦略!)
  • DTYPE列区分タイプ.
  • 勘定科目の関連付けのマッピング分析


    1」会員と注文
  • 一対多、多対一の双方向関係!
  • 関係者を特定するには、外来キーのあるオーダーを関係者のオーナーとした方が良い!
    (表中**無条件に「マルチ」に外部キーが存在します!)
  • 従ってOrder.memberORDERS.MEMBER_ID外来鍵とマッピングする.
  • 2」注文商品と注文
  • 多対一双方向関係
  • 外来キー受注商品のうち、受注商品は関連関係のオーナーである.
  • 従ってOrderItem.orderORDER_ITEM.ORDER_ID外来鍵とマッピングする.
  • 3𗞚商品と商品を注文する
  • 多対一一方向関係
  • OrderItem.itemORDER_ITEM.ITEM_ID外来鍵とマッピングする.
  • 4」注文と配送
    =一対一の双方向関係.
  • Order.deliveryORDERS.DELIVERY_ID外来鍵をマッピングします.
  • 5」カテゴリと商品
  • @ManyToManyを使用してマッピングを行います.
    (実務では使用しない@ManyToMany、ここでは多対多関係を例に追加しただけです!)
  • 🏷 エンティティークラスの開発


    📌 リファレンス
  • 説明の便宜上、図元類で開くGetterSetter、できるだけ設計を簡略化
  • 実務ではなるべく開くことを推奨GetterSetter必要なときのみ使用!
  • ✔メンバーエンティティ

    package jpabook.jpashop.domain;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import lombok.Getter;
    import lombok.Setter;
    
    import javax.persistence.*;
    import java.util.ArrayList;
    import java.util.List;
    
    @Entity
    @Getter @Setter
    public class Member {
    
        @Id @GeneratedValue
        @Column(name = "member_id")
        private Long id;
    
        private String name;
    
        @Embedded
        private Address address;
    
        @JsonIgnore
        @OneToMany(mappedBy = "member")
        private List<Order> orders = new ArrayList<>();
    }
    

    ✔Order-受注エンティティ

    package jpabook.jpashop.domain;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import lombok.AccessLevel;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    import org.hibernate.annotations.BatchSize;
    
    import javax.persistence.*;
    import java.time.LocalDateTime;
    import java.util.ArrayList;
    import java.util.List;
    
    import static javax.persistence.FetchType.*;
    
    @Entity
    @Table(name = "orders")
    @Getter @Setter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class Order {
    
        @Id @GeneratedValue
        @Column(name = "order_id")
        private Long id;
    
        @ManyToOne(fetch = LAZY)
        @JoinColumn(name = "member_id")
        private Member member;
    
        @JsonIgnore
        @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
        private List<OrderItem> orderItems = new ArrayList<>();
    
        @JsonIgnore
        @OneToOne(fetch = LAZY, cascade = CascadeType.ALL)
        @JoinColumn(name = "delivery_id")
        private Delivery delivery;
    
        private LocalDateTime orderDate; //주문시간
    
        // Enum 타입은 @Enumerated(EnumType.STRING) 꼭 필요! -> 문자열로 출력
        @Enumerated(EnumType.STRING)
        private OrderStatus status; //주문상태 [ORDER, CANCEL]
    
        // 양방향 일 때 -> 연관관계 편의 메서드
        public void setMember(Member member) {
            this.member = member;
            member.getOrders().add(this);
        }
    
        public void addOrderItem(OrderItem orderItem) {
            orderItems.add(orderItem);
            orderItem.setOrder(this);
        }
    
        public void setDelivery(Delivery delivery) {
            this.delivery = delivery;
            delivery.setOrder(this);
        }
    
      
    }
    

    ✔orderItem-受注商品エンティティ

    package jpabook.jpashop.domain;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import jpabook.jpashop.domain.item.Item;
    import lombok.AccessLevel;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    
    import javax.persistence.*;
    
    import static javax.persistence.FetchType.*;
    
    @Entity
    @Getter @Setter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class OrderItem {
    
        @Id @GeneratedValue
        @Column(name = "order_item_id")
        private Long id;
    
        @ManyToOne(fetch = LAZY)
        @JoinColumn(name = "item_id")
        private Item item;
    
        @JsonIgnore
        @ManyToOne(fetch = LAZY)
        @JoinColumn(name = "order_id")
        private Order order;
    
        private int orderPrice; //주문 가격
        private int count; //주문 수량
    
        //==생성 메서드==//
        public static OrderItem createOrderItem(Item item, int orderPrice, int count) {
            OrderItem orderItem = new OrderItem();
            orderItem.setItem(item);
            orderItem.setOrderPrice(orderPrice);
            orderItem.setCount(count);
    
            item.removeStock(count);
            return orderItem;
        }
    }
    

    ✔¥Item-商品図元

    package jpabook.jpashop.domain.item;
    
    import jpabook.jpashop.domain.Category;
    import jpabook.jpashop.exception.NotEnoughStockException;
    import lombok.Getter;
    import lombok.Setter;
    import org.hibernate.annotations.BatchSize;
    
    import javax.persistence.*;
    import java.util.ArrayList;
    import java.util.List;
    
    @Entity
    
    // 싱글 테이블 전략
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    // 구별할 타입을 정해줌
    @DiscriminatorColumn(name = "dtype")
    @Getter @Setter
    public abstract class Item {
    
        @Id
        @GeneratedValue
        @Column(name = "item_id")
        private Long id;
    
        private String name;
        private int price;
        private int stockQuantity;
    
        @ManyToMany(mappedBy = "items")
        private List<Category> categories = new ArrayList<>();
    }
    

    ✔Delivery-出荷元

    package jpabook.jpashop.domain;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import lombok.Getter;
    import lombok.Setter;
    
    import javax.persistence.*;
    
    import static javax.persistence.FetchType.*;
    
    @Entity
    @Getter @Setter
    public class Delivery {
    
        @Id @GeneratedValue
        @Column(name = "delivery_id")
        private Long id;
    
        @JsonIgnore
        @OneToOne(mappedBy = "delivery", fetch = LAZY)
        private Order order;
    
        @Embedded
        private Address address;
    
        // Enum 타입은 @Enumerated(EnumType.STRING) 꼭 필요! -> 문자열로 출력
        @Enumerated(EnumType.STRING)
        private DeliveryStatus status; //READY, COMP
    }
    

    ✔範疇図元

    package jpabook.jpashop.domain;
    
    import jpabook.jpashop.domain.item.Item;
    import lombok.Getter;
    import lombok.Setter;
    
    import javax.persistence.*;
    import java.util.ArrayList;
    import java.util.List;
    
    import static javax.persistence.FetchType.*;
    
    @Entity
    @Getter @Setter
    public class Category {
    
        @Id @GeneratedValue
        @Column(name = "category_id")
        private Long id;
    
        private String name;
    
        @ManyToMany
        // 중간 테이블 역할
        @JoinTable(name = "category_item",
                joinColumns = @JoinColumn(name = "category_id"),
                inverseJoinColumns = @JoinColumn(name = "item_id"))
        private List<Item> items = new ArrayList<>();
    
        // 내 부모 - 부모는 하나임
        @ManyToOne(fetch = LAZY)
        @JoinColumn(name = "parent_id")
        private Category parent;
    
        // 내 자식 - 자식은 여러명을 가질 수 있음
        @OneToMany(mappedBy = "parent")
        private List<Category> child = new ArrayList<>();
    
        //==연관관계 메서드==//
        public void addChildCategory(Category child) {
            this.child.add(child);
            child.setParent(this);
        }
    }
    
    📌 仕事中は使わない@ManyToMany
  • @ManyToMany便利そうですが、中間表(CATEGORY_ITEM)にコラムを追加することができず、細かい照会が難しいため、実際の作業での使用は限られています.
  • 作成
  • ミドルエンティティ、マッピングCategoryItem@ManyToOne後に使用!
  • 整理後、複数対のマルチマッピングを展開して1対のマルチマッピングに使用!
  • ✔@OneToMany-アドレス値タイプ

    package jpabook.jpashop.domain;
    
    import lombok.Getter;
    
    import javax.persistence.Embeddable;
    
    // JPA의 내장타입 : 어디든 내장될 수 있다는 뜻
    @Embeddable
    @Getter
    public class Address {
    
        private String city;
        private String street;
        private String zipcode;
    
        protected Address() {
        }
    
        public Address(String city, String street, String zipcode) {
            this.city = city;
            this.street = street;
            this.zipcode = zipcode;
        }
    }
    📌 値のタイプは変更できないように設計します.
    除去
  • Addressジェネレータですべての値を初期化し、変更不可のクラスを作成!
  • JPA仕様では、エンティティまたはimbeddyタイプ(@Setter)java基本生成者を@Embeddableまたはdefault constructor
  • publicよりprotectedの方が安全!
  • 🏷 設計上の注意事項


    1πはなるべくセルに対してSetterを使用しない


    ・𐂍𐂍・𐂊𐂊」」」」」」」
  • 後日は尊敬リングでpublic除く!
  • 2▼▼▼すべての関連関係を遅延ロードに設定

  • 即時ローディング(protected)予測が難しく、どのSQLが実行されるかを追跡することも難しい.
    特にJPQLを実行するとN+1の問題がしばしば発生する.
  • 実務では、すべての関連関係を遅延ロードに設定する.
  • 関連するエンティティをデータベースで問い合わせる必要がある場合は、fetch joinまたはエンティティグラフィックス機能を使用します!
  • Setter関係は基本的にインスタントロードであり、ダイレクトディレイロードに設定すべき!
  • 3▼▼▼▼▼▼▼▼▼▼▼▼▼\96

    private List<Order> orders = new ArrayList<>();
  • コレクションは現場で直接初期化するのが安全です.
  • null問題でセーフ!