OneToMany関連クエリーとコレクションクエリーV 3(V 3 0、V 3 1、V 3 2)


以前OneToMany V 2に存在した問題に対して解決策を提案する.
Order->OrderCollectionDTOに変換して応答として送信する部分は同じであるため,これについては議論しない.

V3_0: JOIN FETCH


API

	@GetMapping("api/v3_0/collection-orders")
    private Result ordersV3_0() {
        List<Order> orders = orderRepository.findAllWithItem0();
        List<OrderCollectionDTO> orderDTOS = orders.stream()
                .map(o -> new OrderCollectionDTO(o))
                .collect(Collectors.toList());

        return new Result(orderDTOS);
	}        

findAllWithItem0()

	public List<Order> findAllWithItem0() {
        String query = "select o from Order o" +
                " join fetch o.member m" +
                " join fetch o.delivery d" +
                " join fetch o.orderItems oi" +
                " join fetch oi.item i";
        return em.createQuery(query, Order.class)
                .getResultList();
    }
ordersのインポートにはJOIN FETCHを使用してインポートします.ただし、JOIN FETCHを使用してorderItemsをインポートすると問題が発生します.結果に重複が表示されます.
{
         *     "data": [
         *         {
         *             "orderId": 4,
         *             "username": "userA",
         *             "orderDate": "2022-04-19T11:14:55.235249",
         *             "orderStatus": "ORDER",
         *             "address": {
         *                 (생략)
         *             },
         *             "orderItems": [
         *                 {
         *                     "itemName": "JPA1 Book",
         *                     (생략)
         *                 },
         *                 {
         *                     "itemName": "JPA2 Book",
         *                     (생략)
         *                 }
         *             ]
         *         },
         *         {
         *             "orderId": 4,
         *             "username": "userA",
         *             "orderDate": "2022-04-19T11:14:55.235249",
         *             "orderStatus": "ORDER",
         *             "address": {
         *                 (생략)
         *             },
         *             "orderItems": [
         *                 {
         *                     "itemName": "JPA1 Book",
         *                     (생략)
         *                 },
         *                 {
         *                     "itemName": "JPA2 Book",
         *                     (생략)
         *                 }
         *             ]
         *         },
         *         {
         *             "orderId": 11,
         *             "username": "userB",
         *             "orderDate": "2022-04-19T11:14:55.356448",
         *             "orderStatus": "ORDER",
         *             "address": {
         *                 (생략)
         *             },
         *             "orderItems": [
         *                 {
         *                     "itemName": "Spring1 Book",
         *                     (생략)
         *                 },
         *                 {
         *                     "itemName": "Spring2 Book",
         *                     (생략)
         *                 }
         *             ]
         *         },
         *         {
         *             "orderId": 11,
         *             "username": "userB",
         *             "orderDate": "2022-04-19T11:14:55.356448",
         *             "orderStatus": "ORDER",
         *             "address": {
         *                 (생략)
         *             },
         *             "orderItems": [
         *                 {
         *                     "itemName": "Spring1 Book",
         *                     (생략)
         *                 },
         *                 {
         *                     "itemName": "Spring2 Book",
         *                     (생략)
         *                 }
         *             ]
         *         }
         *     ]
         * }
応答値を表示することで、重複が発生したかどうかを確認できます.この点は分かりやすく、処理されたSQLのようです.
JOIN FETCHを使用してordeItemsをインポートすることを考慮すると、「SELECT*FROM orders o JOIN order itemoiono.order id=oi.order_id;' に似ています.OrdersとOrder itemに以下のピクチャに示すデータが含まれている場合、JOINは4つの合計パターンを返します.

このとき,JPAは調音ごとに異なると考え,重複した結果を生じる.DISTINCTを使用して、これらのエラーを解決します.

V3_1: JOIN FETCH +DISTINCT


API

	@GetMapping("api/v3_1/collection-orders")
    private Result ordersV3_1() {
        List<Order> orders = orderRepository.findAllWithItem1();
        List<OrderCollectionDTO> orderDTOS = orders.stream()
                .map(o -> new OrderCollectionDTO(o))
                .collect(Collectors.toList());

        return new Result(orderDTOS);
  	}

findAllWithItem1()

		public List<Order> findAllWithItem1() {
        String query = "select distinct o from Order o" +
                " join fetch o.member m" +
                " join fetch o.delivery d" +
                " join fetch o.orderItems oi" +
                " join fetch oi.item i";
        return em.createQuery(query, Order.class)
                .getResultList();
    }
createQueryを実行する場合、distinctを使用して重複エンティティのインポートの問題を解決します.
/**
         * (쿼리문:)
         * select distinct order0_.order_id as order_id1_9_0_, member1_.member_id as member_i1_6_1_, delivery2_.delivery_id as delivery1_4_2_, orderitems3_.order_item_id as order_it1_8_3_, item4_.item_id as item_id2_5_4_, order0_.member_id as member_i4_9_0_, order0_.order_date as order_da2_9_0_, order0_.status as status3_9_0_, member1_.city as city2_6_1_, member1_.street as street3_6_1_, member1_.zipcode as zipcode4_6_1_, member1_.username as username5_6_1_, delivery2_.city as city2_4_2_, delivery2_.street as street3_4_2_, delivery2_.zipcode as zipcode4_4_2_, delivery2_.order_id as order_id6_4_2_, delivery2_.status as status5_4_2_, orderitems3_.count as count2_8_3_, orderitems3_.item_id as item_id4_8_3_, orderitems3_.order_id as order_id5_8_3_, orderitems3_.order_price as order_pr3_8_3_, orderitems3_.order_id as order_id5_8_0__, orderitems3_.order_item_id as order_it1_8_0__, item4_.name as name3_5_4_, item4_.price as price4_5_4_, item4_.quantity as quantity5_5_4_, item4_1_.artist as artist1_0_4_, item4_1_.etc as etc2_0_4_, item4_2_.author as author1_1_4_, item4_2_.isbn as isbn2_1_4_, item4_3_.actor as actor1_7_4_, item4_3_.director as director2_7_4_, item4_.dtype as dtype1_5_4_ from orders order0_ inner join member member1_ on order0_.member_id=member1_.member_id
         * inner join delivery delivery2_ on order0_.order_id=delivery2_.order_id
         * inner join order_item orderitems3_ on order0_.order_id=orderitems3_.order_id inner join item item4_ on orderitems3_.item_id=item4_.item_id
         * left outer join album item4_1_ on item4_.item_id=item4_1_.item_id
         * left outer join book item4_2_ on item4_.item_id=item4_2_.item_id
         * left outer join movie item4_3_ on item4_.item_id=item4_3_.item_id
         * => 쿼리문 총 1개 처리
         * 
         * {
         *     "data": [
         *         {
         *             "orderId": 4,
         *             "username": "userA",
         *             "orderDate": "2022-04-19T11:39:58.086423",
         *             "orderStatus": "ORDER",
         *             "address": {
         *                 "city": "seoul",
         *                 "street": "street",
         *                 "zipcode": "12345"
         *             },
         *             "orderItems": [
         *                 {
         *                     "itemName": "JPA1 Book",
         *                     "orderPrice": 20000,
         *                     "count": 20
         *                 },
         *                 {
         *                     "itemName": "JPA2 Book",
         *                     "orderPrice": 40000,
         *                     "count": 40
         *                 }
         *             ]
         *         },
         *         {
         *             "orderId": 11,
         *             "username": "userB",
         *             "orderDate": "2022-04-19T11:39:58.193136",
         *             "orderStatus": "ORDER",
         *             "address": {
         *                 "city": "ulsan",
         *                 "street": "blvd",
         *                 "zipcode": "22345"
         *             },
         *             "orderItems": [
         *                 {
         *                     "itemName": "Spring1 Book",
         *                     "orderPrice": 10000,
         *                     "count": 1
         *                 },
         *                 {
         *                     "itemName": "Spring2 Book",
         *                     "orderPrice": 20000,
         *                     "count": 2
         *                 }
         *             ]
         *         }
         *     ]
         * }
         */
(Distiチンキについては議論が終わりましたので、省略してください.https://velog.io/@k_ms1998/JPA-JPQL-JOIN-FETCH参照)
Distiチンキは繰返し値を返す問題を解決したが,集合がJOIN FETCHであればページングできない.これらの問題を克服するために、V 3 2を見てみましょう.
Springでは、集合がOneToManyまたはManyToManyの場合、ブロックの原因は、返されるエンティティのエンティティ数がJOINのエンティティのエンティティ数よりも多いため、いくつかのエンティティの制限およびオフセット量を正確に決定できないためである.強制的にページングできますが、この場合、すべての値をメモリに保存し、すべての値をメモリに保存するため、エラーが発生する可能性があります.

V3_2: JOIN FETCH + @BatchSize/batch_fetch_size


API

	@GetMapping("api/v3_2/collection-orders")
    private Result ordersV3_2(@RequestParam(value = "offset", defaultValue = "0") int offset,
                              @RequestParam(value = "limit", defaultValue = "100") int limit) {
        // /api/v3_2/collection-orders?offset=1&limit=1
        List<Order> orders = orderRepository.findALlWithOrderMemberPaging(offset, limit); //ManyToOne, OneToOne 관계 모두 JOIN FETCH && Paging
        List<OrderCollectionDTO> orderDTOS = orders.stream()
                .map(o -> new OrderCollectionDTO(o))
      
        return new Result(orderDTOS);
        }

findALlWithOrderMemberPaging(offset, limit)

public List<Order> findALlWithOrderMemberPaging(int offset, int limit) {
        String query = "select o from Order o" +
                " join fetch o.member m" +
                " join fetch o.delivery d";

        return em.createQuery(query, Order.class)
                .setFirstResult(offset)
                .setMaxResults(limit)
                .getResultList();
    }
ManyToOneまたはOneToOneの場合は、JOIN FETCHを実行した結果の数が、エンティティに格納されている凡例の数を超えてはならないため、ページングを行うことができます.したがって、ページングが必要な場合は、JOIN FETCHを使用してManyToOneとOneToOneに関連付けられたエンティティをページングします.
ただし、ページング時にOneToMany関係の値はJOIN FETCHを使用できないため、batch fetch size/@BastchSizeを使用してパフォーマンスの最適化を行うことができます.
この場合、実際に処理されるクエリ文はV 3 1よりも多くなりますが、データベースへのアクセス回数が1回で終わる可能性があるため、パフォーマンスが最適化されます.V 2アクセスデータベース1+5 N,V 3 1アクセスデータベース1,V 3 2アクセスデータベース1+1.
/**
		* (쿼리문)
         * 1. select order0_.order_id as order_id1_9_0_, member1_.member_id as member_i1_6_1_, delivery2_.delivery_id as delivery1_4_2_, order0_.member_id as member_i4_9_0_, order0_.order_date as order_da2_9_0_, order0_.status as status3_9_0_, member1_.city as city2_6_1_, member1_.street as street3_6_1_, member1_.zipcode as zipcode4_6_1_, member1_.username as username5_6_1_, delivery2_.city as city2_4_2_, delivery2_.street as street3_4_2_, delivery2_.zipcode as zipcode4_4_2_, delivery2_.order_id as order_id6_4_2_, delivery2_.status as status5_4_2_ from orders order0_
         * inner join member member1_ on order0_.member_id=member1_.member_id
         * inner join delivery delivery2_ on order0_.order_id=delivery2_.order_id limit ?
         *
         * 2. select orderitems0_.order_id as order_id5_8_1_, orderitems0_.order_item_id as order_it1_8_1_, orderitems0_.order_item_id as order_it1_8_0_, orderitems0_.count as count2_8_0_, orderitems0_.item_id as item_id4_8_0_, orderitems0_.order_id as order_id5_8_0_, orderitems0_.order_price as order_pr3_8_0_ from order_item orderitems0_ where orderitems0_.order_id
         * in (?, ?)
         *
         * 3. select item0_.item_id as item_id2_5_0_, item0_.name as name3_5_0_, item0_.price as price4_5_0_, item0_.quantity as quantity5_5_0_, item0_1_.artist as artist1_0_0_, item0_1_.etc as etc2_0_0_, item0_2_.author as author1_1_0_, item0_2_.isbn as isbn2_1_0_, item0_3_.actor as actor1_7_0_, item0_3_.director as director2_7_0_, item0_.dtype as dtype1_5_0_ from item item0_ left outer join album item0_1_ on item0_.item_id=item0_1_.item_id
         * left outer join book item0_2_ on item0_.item_id=item0_2_.item_id
         * left outer join movie item0_3_ on item0_.item_id=item0_3_.item_id where item0_.item_id
         * in (?, ?, ?, ?)
         *
         * batch_fetch_size를 설정했기 때문에 2번과 3번 쿼리문에서 in()이 처리됩니다
*/
  • applicatin.ymlから
  • まで
             spring:
             jpa:
             properties:
             hibernate:
             	default_batch_fetch_size: 100
    OR
    2. @BatchSize(size = 100)
    必要に応じてsize値を変更するには、2つの方法で設定できます.
    通常、100~1000の値を選択することをお勧めします.サイズに関係なく、クライアントのメモリ使用量は同じですが、値が大きすぎるとデータベースに瞬時負荷が発生する可能性があるため、値を調整できます.