実戦使用1 JPA開発(発注)

13874 ワード

1.注文、注文商品実体


*オーダードメインの実装機能


1)商品注文2)注文履歴照会3)注文キャンセル

👀関連便利な方法



✔はどこですか.
答)通常、ビジネスロジックの中心エンティティで使用することが望ましい.
受注は、Orderエンティティとメンバーエンティティの関係であるため、受注が中心であるため、Orderエンティティに書き込まれます.
どうして.
答え)双方向なので、両方に値をつける

追加)setXXX()メソッドとaddXXX()メソッド



サービスロジックを作成するときに、2つの中から1つを選ぶと分かりやすい

ex)


(1)
//Delivery와 Order 관계
//delivery.getOrder()시 ->delivery 엔티티에서  order 객체를 반환함
    public void setDelivery(Delivery delivery) { 
        this.delivery = delivery; //배송지 설정
        //Order 객체를 반환 => add()를 넣지 x
        delivery.setOrder(this);
    }
オブジェクトが
  • 2delivery.getOrder(this)に戻ると、セットのadd()は使用できません.したがって、delivery.setOrder(this)、例えばset()を使用する必要がある場合があります.
  • (2)
      //Member와 Order 엔티티
        public void setMember(Member member) { 
            this.member = member;
            //Member엔티티에선 List<Order> orders 리스트 반환 => add()를 사용o
            member.getOrders().add(this);
        }
  • とは対照的に、このsetMember()メソッドは同じset()メソッドですが、member.getORders()が戻ると対応する受注リストが返されるため、->セットadd()が使用できます.
  • 👏 これに対して、私たちはn.問題に返事をしました!

    ※呪文絵元


    1)オーダー生成方法


    方法
  • Orderオブジェクトを生成し,会員,配送先,注文商品カタログにおいて,商品の関連関係に応じて価格を設定する.
  • オーダーステータスは「オーダー」、初期ステータスは
  • です.
  • 現在のスケジュール(createOrder())
    ►生成メソッドを後で変更する場合は、ここでのみ生成メソッドを作成することをお勧めします.
  • 📣 LocalDateTime.now()はjava可変因子です.

    2)業務ロジック


    1.注文取り消し方法



    -OrderItem... orderItems類COMPの場合、出荷済み;異常処理
    -出荷されていない場合は、現在の受注ステータスをCANCELに変更します.
    受注ステータスのキャンセル
    -注文者に注文をキャンセルする他の商品を通知します.

    3)クエリーロジック


    1.全受注価格の表示



    ※注文商品図元


    1)オーダー生成方法



    -作成プロセスは簡単ではないため、メソッドとして定義します.
  • なぜItemエンティティにもDeliveryStatusがあるのか、ここでもパラメータとしてpriceが導入されているのか.
    =>割引やクーポン利用の場合があります.
  • 2)業務ロジック


    1)cancel()メソッド


    ItemエンティティのOrderPriceメソッドが呼び出され、addStock()がキャンセルされるため、「在庫数量」が増加します.

    3)クエリーロジック


    1)getTotalPrice()金額照会方法


    orderItemエンティティはorderエンティティの呼び出しを受け入れ、受注商品の数と価格を計算し、値を返します.

    2.受注ライブラリの開発


  • スプリング空孔充填@Repository
  • 「final」メンバーフィールドを含む
  • count(省略)
  • のみを生成する.
  • の検索機能はダイナミッククエリーで、最後は
  • です.

    3.開発オーダーサービス


    📣注意!例を簡略化するために、注文サービスではこのような商品しか受け付けていません^^

    *サービスで実装される機能)

  • 注文2.オーダーのキャンセルオーダー検索
  • 1)カスケードとsave()



    👏 カスケードを使用する理由


    1.Cascadeが利用できる条件は1)同じライフサイクル,2)参照の所有者がプライベート所有者である場合,2つの条件を満たすために使用される.しかし、最も重要なのは、今と未来も他の場所で参考にならないことです.
    2.両方型、関連関係マスターなどとは無関係.3.境界検出とカスケードの使用差異が存在します.ここを参照してください.

    👏 Cascadeはどこですか。



    対応するOrderエンティティでは、OrderItemで@RequireArgsConstructが検索されます.
    =>すぐにカスケードの場所を全部飛ばしますcascade = CacadeType.ALL.=>orderItem、提供済みpersist()*(結論):
    1.カスケードは、保存と削除の効果を他のエンティティに伝播します.
    2.理解していなければ、後でこのような状況を見たときに書きます^^

    2)変更の検出

      /** 주문 취소 */
        //주문 아이디만 알면 되네
        @Transactional
        public void cancelOrder(Long orderId) {
    
            //주문 id로 해당 주문엔티티 조회
            Order order = (Order) orderRepository.findOne(orderId);
    
            //주문 취소 메서드
            //*변경감지 (jpa가 데이터가 변경된 것을 감지하여 DB에 쿼리를 날려줌)
            //->cancel()메서드 내 orderStatus()변경으로 자동으로 감지, addStock()변경 자동감지
            order.cancel();
        }
    自動検出をcancel()メソッドのorderStatus()で変更し、addStock()で自動検出を変更します.
    =>継続的な状態でデータの変更が検出され、データベースにクエリーが発行されます.
    📣 もう一度読んで理解しなければ。

    3)@NoArgConstructor(access=accessLevel.PROTED)の追加



    オブジェクトのメンテナンスが難しく、setで次から次へとやっている人もいます.複雑にならないように.
    書き込み@NotargConstructor(access=AccessLevel.PROTED)は、サービスで直接実装する必要はなく、Entityで作成した方法のように使用します.

    4.受注機能のテスト


    👀テスト要件

  • 件を正常に注文する必要があります.
  • 商品の注文は在庫数量を超えてはならない.
  • オーダー
  • を正常にキャンセルする必要があります.

    👀 OrderServiceTest

    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @Transactional
    public class OrderServiceTest {
    
        @PersistenceContext EntityManager em;
        @Autowired OrderService orderService;
        @Autowired OrderRepository orderRepository;
    
        @Test
        public void 상품주문() throws Exception {
            //Given
            Member member = createMember();
            Item item = createBook("시골 JPA", 10000, 10); //이름, 가격, 재고
    
            int orderCount = 2;
    
    
            //When
            //주문하기
            Long orderId = orderService.order(member.getId(), item.getId(),
                    orderCount);
    
    
            //Then
            Order getOrder = orderRepository.findOne(orderId);
    
            //assertEquals("메세지", 기댓값, 실제값)
            assertEquals("상품 주문시 상태는 ORDER",OrderStatus.ORDER,
                    getOrder.getStatus());
            assertEquals("주문한 상품 종류 수가 정확해야 한다.",1,
                    getOrder.getOrderItems().size());
            assertEquals("주문 가격은 가격 * 수량이다.", 10000 * 2,
                    getOrder.getTotalPrice());
            assertEquals("주문 수량만큼 재고가 줄어야 한다.",8, item.getStockQuantity());
        }
    
        @Test
        public void 주문취소() {
    
            //Given [~이 주어졌을때]
            Member member = createMember();
            Item item = createBook("시골 JPA", 10000, 10); //이름, 가격, 재고
            int orderCount = 2;
    
            //주문 해놓는거까지 준비
            Long orderId = orderService.order(member.getId(), item.getId(),
                    orderCount);
    
            //When
            orderService.cancelOrder(orderId); //주문 취소
    
            //Then
            Order getOrder = orderRepository.findOne(orderId);//해당 주문id
    
            assertEquals("주문 취소시 상태는 CANCEL 이다.",OrderStatus.CANCEL,
                    getOrder.getStatus());
            assertEquals("주문이 취소된 상품은 그만큼 재고가 증가해야 한다.", 10,
                    item.getStockQuantity()); //주문이 취소되었으므로 재고 자체가 10개로 원래대로 복구
    
        }
    
        //NotEnoughStock... ->이 예외가 터져야 함
        @Test(expected = NotEnoughStockException.class)
        public void 상품주문_재고수량초과() throws Exception {
    
    
            //Given
            Member member = createMember();
            Item item = createBook("시골 JPA", 10000, 10); //이름, 가격, 재고
            int orderCount = 11; //재고보다 많은 수량
    
            //When(~을 실행하면) [에러발생 부분]
            orderService.order(member.getId(), item.getId(), orderCount);
    
            //Then(~결과가 나옴)
            //테스트가 성공한다면 -> 여기까지 오면 안돼
            fail("재고 수량 부족 예외가 발생해야 한다.");
        }
    
    
        //==given에 쓰이던 것들을 다른 테스트에서도 쓰여야 하니까 걍 따로 메서드로 생성==//
        
        private Member createMember() {
            Member member = new Member();
            member.setName("회원1");
            member.setAddress(new Address("서울", "강가", "123-123"));
            em.persist(member);
            return member;
        }
    
    
        private Book createBook(String name, int price, int stockQuantity) {
            Book book = new Book();
            book.setName(name);
            book.setStockQuantity(stockQuantity);
            book.setPrice(price);
            em.persist(book);
            return book;
        }

    📣 知るところ

  • DBが何であれ、ユニットテストを行う必要があります.
  • コアは、スプリングコンテナや特定のデータベースなどの下位インフラストラクチャに依存せず、コアビジネスロジック(サービス、エンティティ)をテストするだけです.
  • リポジトリを使用してレポートを処理します.
  • 最終的には、これらの重要なビジネスロジックをユニットテストすることが重要です.ユニットテストについて講師の考えをより詳細に述べた.

    5.受注検索機能の開発


    📣動的クエリーとは?


    (1)実行時にクエリ文を生成して実行するクエリ文.クエリ文が変化するか変化しないかによって、変化しない限り静的クエリとみなされ、変化すると動的クエリとみなされます.
  • 動的クエリーを使用する場合、ほとんどはクエリー文としてテキスト文を使用し、実行するたびに
  • を実行するためにテキストクエリー文を変更します.

    1)動的クエリの2つの方法


    1.JPQL処理の使用

    /**
         * 검색기능 -> 동적쿼리
         *  (1) JPQL 방법
         *JPA Criteria(JpaSpecificationExecutor 포함)을 사용하지 마시고,
         * 단순해도 다른 방법으로 푸시는 것을 권장합니다.
         * !!! 현재 가장 좋은 방법은 Querydsl이라는 기술을 사용하는 것입니다.
         * 자바 코드로 쿼리를 작성해서 컴파일 시점에 오류를 잡아주고,
         * 자바 코드를 활용해서 매우 깔끔하게 동적 쿼리를 작성할 수 있습니다.
         */
        public List<Order> findAllByString(OrderSearch orderSearch) {
    
            //jpql을 동적으로 만들기 위해
            //(1)JPQL 문자로 만드는 방법 -> 지옥의 방법
            String jpql = "select o From Order o join o.member m";
            boolean isFirstCondition = true;
    
            //주문 상태 검색
            if (orderSearch.getOrderStatus() != null) {
                if (isFirstCondition) {
                    jpql += " where";
                    isFirstCondition = false;
                } else {
                    jpql += " and";
                }
                jpql += " o.status = :status";
            }
    
    
            //회원 이름 검색
            //hasText() 값이 잇다면
            if (StringUtils.hasText(orderSearch.getMemberName())) {
                if (isFirstCondition) {
                    jpql += " where";
                    isFirstCondition = false;
                } else {
                    jpql += " and";
                }
    
                jpql += " m.name like :name";
    
            }
    
    
            TypedQuery<Order> query = em.createQuery(jpql, Order.class)
                    .setMaxResults(1000); //최대 1000건
    
    
            if (orderSearch.getOrderStatus() != null) {//orderStatus가 있다면
                query = query.setParameter("status", orderSearch.getOrderStatus());
            }
    
            if (StringUtils.hasText(orderSearch.getMemberName())) {//Member가 있다면
                query = query.setParameter("name", orderSearch.getMemberName());
            }
    
    
            return query.getResultList();
        }
    
        
    和弦を見るだけで長くて疲れます...内容が複雑すぎる.

    _2. JPA Criteria

    /**
         * (2) JPA Criteria -> 이것도 어려움;
         * jpql을 자바코드로 작성하게 해주게 '표준 방법임'
         단점: 유지보수가 안좋음-> 직관적이지 x
         */
        public List<Order> findAllByCriteria(OrderSearch orderSearch) {
            CriteriaBuilder cb = em.getCriteriaBuilder();
            CriteriaQuery<Order> cq = cb.createQuery(Order.class);
            Root<Order> o = cq.from(Order.class);
            Join<Order, Member> m = o.join("member", JoinType.INNER); //회원과 조인
            List<Predicate> criteria = new ArrayList<>();
    
            //주문 상태 검색
            if (orderSearch.getOrderStatus() != null) {
                Predicate status = cb.equal(o.get("status"),
                        orderSearch.getOrderStatus());
    
                criteria.add(status);
    
            }
    
            //회원 이름 검색
            if (StringUtils.hasText(orderSearch.getMemberName())) {
                Predicate name = cb.like(m.<String>get("name"), "%" +
                        orderSearch.getMemberName() + "%");
    
                criteria.add(name);
            }
    
            cq.where(cb.and(criteria.toArray(new Predicate[criteria.size()])));
            TypedQuery<Order> query = em.createQuery(cq).setMaxResults(1000); //최대1000건
    
            return query.getResultList();
    
    
        }
    (1)JPA Criteriaも長すぎて可読性が悪く、jpa規格(2)JPA Criteriaのコードも複雑すぎて直感的ではなく理解しにくい.

    😥 結論!


    多くの開発者は「ダイナミッククエリー」を考えています.彼らは現在最もクールな解決方法はQuerydslだと言っています.