実戦使用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);
}
オブジェクトがdelivery.getOrder(this)
に戻ると、セットのadd()は使用できません.したがって、delivery.setOrder(this)
、例えばset()
を使用する必要がある場合があります. //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()
が使用できます.※呪文絵元
1)オーダー生成方法
方法
createOrder()
)►生成メソッドを後で変更する場合は、ここでのみ生成メソッドを作成することをお勧めします.
LocalDateTime.now()
はjava可変因子です.2)業務ロジック
1.注文取り消し方法
-
OrderItem... orderItems
類COMPの場合、出荷済み;異常処理-出荷されていない場合は、現在の受注ステータスをCANCELに変更します.
受注ステータスのキャンセル
-注文者に注文をキャンセルする他の商品を通知します.
3)クエリーロジック
1.全受注価格の表示
※注文商品図元
1)オーダー生成方法
-作成プロセスは簡単ではないため、メソッドとして定義します.
DeliveryStatus
があるのか、ここでもパラメータとしてprice
が導入されているのか.=>割引やクーポン利用の場合があります.
2)業務ロジック
1)cancel()メソッド
Itemエンティティの
OrderPrice
メソッドが呼び出され、addStock()
がキャンセルされるため、「在庫数量」が増加します.3)クエリーロジック
1)getTotalPrice()金額照会方法
orderItemエンティティはorderエンティティの呼び出しを受け入れ、受注商品の数と価格を計算し、値を返します.
2.受注ライブラリの開発
count
(省略)3.開発オーダーサービス
📣注意!例を簡略化するために、注文サービスではこのような商品しか受け付けていません^^
*サービスで実装される機能)
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;
}
📣 知るところ
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だと言っています.
Reference
この問題について(実戦使用1 JPA開発(発注)), 我々は、より多くの情報をここで見つけました https://velog.io/@dabeen-jung/실전-활용1-JPA-개발주문テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol