精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Java 中 N+1 問題的集成測試

開發 前端
事實上,定期測試可以防止 N+1 問題。這是一個很好的機會,可以保護那些對性能至關重要的代碼部分。

N+1問題:N+1問題是指在使用關系型數據庫時,在獲取一組對象及其關聯對象時,產生額外的數據庫查詢的問題。其中N表示要獲取的主對象的數量,而在獲取每個主對象的關聯對象時,會產生額外的1次查詢。

N+1問題是很多項目中的通病。遺憾的是,直到數據量變得龐大時,我們才注意到它。不幸的是,當處理 N + 1 問題成為一項難以承受的任務時,代碼可能會達到了一定規模。

在這篇文章中,我們將開始關注以下幾點問題:

  1. 如何自動跟蹤N+1問題?
  2. 如何編寫測試來檢查查詢計數是否超過預期值?

N + 1 問題的一個例子

假設我們正在開發管理動物園的應用程序。在這種情況下,有兩個核心實體:Zoo和Animal。請看下面的代碼片段:

@Entity
@Table(name = "zoo")
public class Zoo {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "zoo", cascade = PERSIST)
    private List<Animal> animals = new ArrayList<>();
}

@Entity
@Table(name = "animal")
public class Animal {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "zoo_id")
    private Zoo zoo;

    private String name;
}

現在我們想要檢索所有現有的動物園及其動物??纯碯ooService下面的代碼。

@Service
@RequiredArgsConstructor
public class ZooService {
    private final ZooRepository zooRepository;

    @Transactional(readOnly = true)
    public List<ZooResponse> findAllZoos() {
        final var zoos = zooRepository.findAll();
        return zoos.stream()
                   .map(ZooResponse::new)
                   .toList();
    }
}

此外,我們要檢查一切是否順利進行。簡單的集成測試:

@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE)
@Transactional(propagation = NOT_SUPPORTED)
@Testcontainers
@Import(ZooService.class
class ZooServiceTest {
    @Container
    static final PostgreSQLContainer<?> POSTGRES = new PostgreSQLContainer<>("postgres:13");

    @DynamicPropertySource
    static void setProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", POSTGRES::getJdbcUrl);
        registry.add("spring.datasource.username", POSTGRES::getUsername);
        registry.add("spring.datasource.password", POSTGRES::getPassword);
    }

    @Autowired
    private ZooService zooService;
    @Autowired
    private ZooRepository zooRepository;

    @Test
    void shouldReturnAllZoos() {
        /* data initialization... */
        zooRepository.saveAll(List.of(zoo1, zoo2));

        final var allZoos = assertQueryCount(
            () -> zooService.findAllZoos(),
            ofSelects(1)
        );

        /* assertions... */
        assertThat(
            ...
        );
    }
}

測試成功通過。但是,如果記錄 SQL 語句,會注意到以下幾點:

-- selecting all zoos
select z1_0.id,z1_0.name from zoo z1_0
-- selecting animals for the first zoo
select a1_0.zoo_id,a1_0.id,a1_0.name from animal a1_0 where a1_0.zoo_id=?
-- selecting animals for the second zoo
select a1_0.zoo_id,a1_0.id,a1_0.name from animal a1_0 where a1_0.zoo_id=?

如所見,我們select對每個 present 都有一個單獨的查詢Zoo。查詢總數等于所選動物園的數量+1。因此,這是N+1問題。

這可能會導致嚴重的性能損失。尤其是在大規模數據上。

自動跟蹤 N+1 問題

當然,我們可以自行運行測試、查看日志和計算查詢次數,以確定可行的性能問題。無論如何,這效率很低。。

有一個非常高效的庫,叫做datasource-proxy。它提供了一個方便的 API 來javax.sql.DataSource使用包含特定邏輯的代理來包裝接口。例如,我們可以注冊在查詢執行之前和之后調用的回調。該庫還包含開箱即用的解決方案來計算已執行的查詢。我們將對其進行一些改動以滿足我們的需要。

查詢計數服務

首先,將庫添加到依賴項中:

implementation "net.ttddyy:datasource-proxy:1.8"

現在創建QueryCountService. 它是保存當前已執行查詢計數并允許您清理它的單例。請看下面的代碼片段。

@UtilityClass
public class QueryCountService {
    static final SingleQueryCountHolder QUERY_COUNT_HOLDER = new SingleQueryCountHolder();

    public static void clear() {
        final var map = QUERY_COUNT_HOLDER.getQueryCountMap();
        map.putIfAbsent(keyName(map), new QueryCount());
    }

    public static QueryCount get() {
        final var map = QUERY_COUNT_HOLDER.getQueryCountMap();
        return ofNullable(map.get(keyName(map))).orElseThrow();
    }

    private static String keyName(Map<String, QueryCount> map) {
        if (map.size() == 1) {
            return map.entrySet()
                       .stream()
                       .findFirst()
                       .orElseThrow()
                       .getKey();
        }
        throw new IllegalArgumentException("Query counts map should consists of one key: " + map);
    }
}

在那種情況下,我們假設_DataSource_我們的應用程序中有一個。這就是_keyName_函數否則會拋出異常的原因。但是,代碼不會因使用多個數據源而有太大差異。

將SingleQueryCountHolder所有QueryCount對象存儲在常規ConcurrentHashMap.

相反,_ThreadQueryCountHolder_將值存儲在_ThreadLocal_對象中。但是_SingleQueryCountHolder_對于我們的情況來說已經足夠了。

API 提供了兩種方法。該get方法返回當前執行的查詢數量,同時clear將計數設置為零。

BeanPostProccessor 和 DataSource 代理

現在我們需要注冊QueryCountService以使其從 收集數據DataSource。在這種情況下,BeanPostProcessor 接口就派上用場了。請看下面的代碼示例。

@TestComponent
public class DatasourceProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof DataSource dataSource) {
            return ProxyDataSourceBuilder.create(dataSource)
                       .countQuery(QUERY_COUNT_HOLDER)
                       .build();
        }
        return bean;
    }
}

我用注釋標記類_@TestComponent_并將其放入_src/test_目錄,因為我不需要對測試范圍之外的查詢進行計數。

如您所見,這個想法很簡單。如果一個 bean 是DataSource,則將其包裹起來ProxyDataSourceBuilder并將QUERY_COUNT_HOLDER值作為QueryCountStrategy.

最后,我們要斷言特定方法的已執行查詢量??纯聪旅娴拇a實現:

@UtilityClass
public class QueryCountAssertions {
    @SneakyThrows
    public static <T> T assertQueryCount(Supplier<T> supplier, Expectation expectation) {
        QueryCountService.clear();
        final var result = supplier.get();
        final var queryCount = QueryCountService.get();
        assertAll(
            () -> {
                if (expectation.selects >= 0) {
                    assertEquals(expectation.selects, queryCount.getSelect(), "Unexpected selects count");
                }
            },
            () -> {
                if (expectation.inserts >= 0) {
                    assertEquals(expectation.inserts, queryCount.getInsert(), "Unexpected inserts count");
                }
            },
            () -> {
                if (expectation.deletes >= 0) {
                    assertEquals(expectation.deletes, queryCount.getDelete(), "Unexpected deletes count");
                }
            },
            () -> {
                if (expectation.updates >= 0) {
                    assertEquals(expectation.updates, queryCount.getUpdate(), "Unexpected updates count");
                }
            }
        );
        return result;
    }
}

該代碼很簡單:

  1. 將當前查詢計數設置為零。
  2. 執行提供的 lambda。
  3. 將查詢計數給定的Expectation對象。
  4. 如果一切順利,返回執行結果。

此外,您還注意到了一個附加條件。如果提供的計數類型小于零,則跳過斷言。不關心其他查詢計數時,這很方便。

該類Expectation只是一個常規數據結構??聪旅嫠穆暶鳎?/p>

@With
@AllArgsConstructor
@NoArgsConstructor
public static class Expectation {
    private int selects = -1;
    private int inserts = -1;
    private int deletes = -1;
    private int updates = -1;

    public static Expectation ofSelects(int selects) {
        return new Expectation().withSelects(selects);
    }

    public static Expectation ofInserts(int inserts) {
        return new Expectation().withInserts(inserts);
    }

    public static Expectation ofDeletes(int deletes) {
        return new Expectation().withDeletes(deletes);
    }

    public static Expectation ofUpdates(int updates) {
        return new Expectation().withUpdates(updates);
    }
}

最后的例子

讓我們看看它是如何工作的。首先,我在之前的 N+1 問題案例中添加了查詢斷言。看下面的代碼塊:

final var allZoos = assertQueryCount(
    () -> zooService.findAllZoos(),
    ofSelects(1)
);

不要忘記
_DatasourceProxyBeanPostProcessor_在測試中作為 Spring bean 導入。

如果我們重新運行測試,我們將得到下面的輸出。

Multiple Failures (1 failure)
    org.opentest4j.AssertionFailedError: Unexpected selects count ==> expected: <1> but was: <3>
Expected :1
Actual   :3

所以,確實有效。我們設法自動跟蹤 N+1 問題。是時候用 替換常規選擇了JOIN FETCH。請看下面的代碼片段。

public interface ZooRepository extends JpaRepository<Zoo, Long> {
    @Query("FROM Zoo z LEFT JOIN FETCH z.animals")
    List<Zoo> findAllWithAnimalsJoined();
}

@Service
@RequiredArgsConstructor
public class ZooService {
    private final ZooRepository zooRepository;

    @Transactional(readOnly = true)
    public List<ZooResponse> findAllZoos() {
        final var zoos = zooRepository.findAllWithAnimalsJoined();
        return zoos.stream()
                   .map(ZooResponse::new)
                   .toList();
    }
}

讓我們再次運行測試并查看結果:

這意味著正確地跟蹤了 N + 1 個問題。此外,如果查詢數量等于預期數量,則它會成功通過。

結論

事實上,定期測試可以防止 N+1 問題。這是一個很好的機會,可以保護那些對性能至關重要的代碼部分。

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2010-09-06 09:03:17

SQLselect語句

2010-08-23 09:50:20

Ruby on Rai

2018-11-30 08:38:24

裁員互聯網電商

2019-05-24 08:36:33

MySQL查詢SQL

2009-02-26 08:42:31

聯想裁員賠償標準

2020-11-16 07:26:25

賠償碼農

2021-02-02 13:35:48

React插件N+1

2021-07-16 08:29:41

項目React必備插件

2021-11-03 16:00:40

SQL流量序號

2021-04-06 11:50:30

SQL數據統計

2025-10-13 07:33:26

2009-03-09 09:19:00

802.11n集成

2022-12-25 16:38:14

后端KuberneteIter8

2022-03-18 09:42:54

JavaString

2009-03-17 09:56:00

802.11n測試無線網絡

2022-02-09 07:29:53

打印排列解法循環解法

2023-05-27 08:29:40

中臺阿里巴巴架構

2021-08-10 07:57:03

算法鏈表倒數

2021-04-01 15:42:45

人工智能軟件測試

2020-07-07 07:33:12

Java單元集成
點贊
收藏

51CTO技術棧公眾號

狠狠做六月爱婷婷综合aⅴ| 波多野结衣中文字幕久久| 手机精品视频在线观看| www.日韩系列| 在线精品视频播放| av亚洲一区二区三区| 最新中文字幕一区二区三区| 国产成人精品福利一区二区三区| 日本天堂网在线| 久久亚洲影视| 亚洲激情视频网| 东京热加勒比无码少妇| а天堂中文在线官网| 99视频有精品| 3d蒂法精品啪啪一区二区免费| 日韩大片免费在线观看| 国产精品久久久久无码av| 亚洲国产成人精品久久久国产成人一区| 国产极品美女高潮无套久久久| 欧美成人hd| 久久一区二区视频| av资源一区二区| 亚洲图片小说视频| 午夜在线精品| 色综合久久久久久中文网| 国产jjizz一区二区三区视频| 国产精品sss在线观看av| 欧美亚洲国产一区二区三区 | 在线看的黄色网址| 国产伦理精品| 亚洲视频一二区| 三区精品视频观看| 网站黄在线观看| 精彩视频一区二区三区| 国产97在线观看| 亚洲 欧美 视频| 欧美午夜在线| 美日韩精品免费视频| 欧美人与禽zoz0善交| 色哟哟精品丝袜一区二区| 精品日韩一区二区| 日本xxxx免费| 欧美成人精品午夜一区二区| 欧美午夜精品一区二区三区| 国产一区亚洲二区三区| 中文不卡1区2区3区| 亚洲成av人**亚洲成av**| 日韩成人手机在线| 国产精品一区二区久久国产| 一区二区三区四区五区精品 | 在线免费观看的av| 国产精品久久久久久久久图文区| 久热国产精品视频一区二区三区| 欧美 日韩 国产 成人 在线| 国产精品996| 91欧美激情另类亚洲| 亚洲天堂一二三| 久久国产乱子精品免费女| 国产精品久久久久久久久久久久久| 天天干在线播放| 亚洲视频播放| 欧美在线xxx| 亚洲天堂男人av| 丝袜亚洲另类丝袜在线| 国产999精品久久久| 免费看日批视频| 日韩福利电影在线| 国产精品日日摸夜夜添夜夜av| 成人免费视频国产免费| 日本成人超碰在线观看| 国产欧美va欧美va香蕉在线| 国产欧美日韩成人| 国产成人免费av在线| 97人人模人人爽视频一区二区| www.精品久久| 99久久精品情趣| 日本一区二区三区四区高清视频| 99re在线视频| 亚洲美女视频在线观看| 黄色a级片免费看| 亚洲美女炮图| 欧美唯美清纯偷拍| 99re6在线观看| 亚洲一区电影| 国产视频精品va久久久久久| jizz中文字幕| 欧美精选一区| 欧美亚洲国产成人精品| 国产裸体美女永久免费无遮挡| 精品一区二区影视| 精品日产一区2区三区黄免费| 欧美孕妇孕交| 亚洲欧美日韩国产成人精品影院| 久久久亚洲国产精品| 女生影院久久| 91精品免费在线观看| 亚洲永久无码7777kkk| 久久亚洲影视| 538国产精品视频一区二区| 少妇又紧又色又爽又刺激视频 | 免费在线观看污视频| 中文字幕成人网| 免费看毛片的网址| a屁视频一区二区三区四区| 日韩精品一区国产麻豆| 波多野结衣 在线| 91超碰成人| 日本精品久久久久影院| 亚洲AV无码乱码国产精品牛牛 | 亚洲精品一区在线观看| 欧美性受xxxx黑人| 亚洲一区二区三区高清不卡| 成人黄色免费看| 青青草娱乐在线| 一区二区三区在线免费播放| www.99av.com| 国偷自产av一区二区三区| 最近2019好看的中文字幕免费| 日本三级免费看| 韩国三级在线一区| 日韩av电影在线观看| 高清电影在线观看免费| 91精品欧美久久久久久动漫| 成人小视频免费看| 久久久精品网| 精品日韩美女| bl在线肉h视频大尺度| 91精品国产综合久久久久久久| 制服 丝袜 综合 日韩 欧美| 在线视频精品| 国产欧美在线一区二区| 四虎影视成人| 欧美一区二区观看视频| 国产极品视频在线观看| 日本人妖一区二区| 欧美污视频久久久| 成人小电影网站| 日韩av综合中文字幕| 久久免费小视频| 国产精品99久| 日本黄色片一级片| 亚洲精选av| 欧美激情乱人伦| 成人黄色免费视频| 亚洲黄色性网站| 下面一进一出好爽视频| 久久大综合网| 成人午夜在线观看| 久久77777| 91精品国产综合久久香蕉麻豆| 国产精品视频看看| 久草精品在线观看| 激情图片qvod| 一区二区三区在线资源| 久久久久国色av免费观看性色| wwwav在线播放| 亚洲一区在线播放| 在线黄色免费网站| 亚洲永久免费精品| 欧美一区二区三区四区在线观看地址 | 久久蜜桃一区二区| 99视频精品免费| 日产午夜精品一线二线三线| 国产精品自产拍在线观| 日本在线视频站| 欧美一级国产精品| 国产真实的和子乱拍在线观看| 成人网男人的天堂| 一本大道熟女人妻中文字幕在线| 九九综合久久| 成人日韩av在线| 日韩精品卡一| 日韩经典中文字幕| 成人黄色三级视频| 亚洲视频免费观看| 国产高清成人久久| 日日摸夜夜添夜夜添亚洲女人| 尤物国产精品| eeuss国产一区二区三区四区| 久久久综合av| 国产裸舞福利在线视频合集| 欧美高清视频在线高清观看mv色露露十八| www.97视频| 成人精品一区二区三区中文字幕| 免费黄色福利视频| 91精品一区二区三区综合| 国产精品国产精品国产专区蜜臀ah| 亚洲欧美se| 北条麻妃一区二区三区中文字幕| 高h放荡受浪受bl| 色乱码一区二区三区88| 日本福利片在线观看| 国产99久久久久久免费看农村| 国内外成人激情视频| 日韩欧美一区二区三区在线视频| 国产福利久久| 成人在线视频免费看| 欧美激情第6页| a黄色在线观看| 精品久久久久久亚洲综合网| 国产91av在线播放| 亚洲国产欧美一区二区三区丁香婷| 在线观看福利片| 国产成人av网站| 高清av免费看| 午夜在线视频一区二区区别| 大桥未久一区二区三区| 国产一区二区三区四区| 国产成人精品免费视频大全最热 | 91文字幕巨乱亚洲香蕉| 中文字幕乱码在线播放| 欧美大胆在线视频| 在线a免费看| 亚洲男人的天堂在线| 性生交生活影碟片| 欧美人动与zoxxxx乱| 黄色片网站在线免费观看| 亚洲一区在线看| 91插插插插插插| 国产精品三级电影| 久久久久久久久久久久| 99视频一区二区| 一区二区在线免费观看视频| 久久超碰97中文字幕| 国产精品天天av精麻传媒| 亚洲少妇在线| 久久久久久久午夜| 欧美日本中文| 最新av网址在线观看| 久久一级电影| 亚洲欧洲另类精品久久综合| 神马久久一区二区三区| 精品欧美国产| 欧美日韩一本| 精品视频免费观看| 久久99精品国产自在现线| 成人在线观看网址| 99国产精品免费网站| 91丨九色丨国产| 日韩成人在线看| 51国偷自产一区二区三区的来源| 色成人综合网| 91中文字幕在线| a一区二区三区亚洲| 成人免费福利视频| 欧美日韩黄网站| 999在线观看免费大全电视剧| 国产一区二区av在线| 成人国产精品久久久久久亚洲| 国产精品久久久久77777丨| 国产精品成人国产乱一区| 91天天综合| 国产精品自产拍在线观看中文| 黄色欧美视频| 国产欧美久久一区二区| 一区二区三区无毛| 91亚洲国产成人精品性色| 欧美一级片网址| av色综合网| 欧美三级电影在线| 日本视频一区在线观看| 久久在线播放| 青青视频免费在线观看| 在线成人h网| 国产特级黄色大片| 日本午夜精品视频在线观看| 中文字幕一区久久| 粉嫩av一区二区三区在线播放| 李丽珍裸体午夜理伦片| 91蜜桃视频在线| 你懂得视频在线观看| 亚洲欧美日韩国产一区二区三区| 久久久久久久极品内射| 日韩欧美在线播放| 中文字幕丰满人伦在线| 日韩欧美的一区| 天堂视频中文在线| 色综合影院在线| 狂野欧美激情性xxxx欧美| 欧美在线观看一区二区三区| 国产精品99久久久久久董美香 | 91 视频免费观看| 成人av电影在线播放| 久久精品—区二区三区舞蹈| 国产精品国产三级国产普通话99 | 午夜伦理在线视频| 日本道色综合久久影院| **精品中文字幕一区二区三区| 成人18视频| 欧美极品中文字幕| 四虎4hu永久免费入口| 免费在线播放第一区高清av| 亚洲一级免费在线观看| 成人妖精视频yjsp地址| 免费黄色片网站| 一区二区三区日韩在线观看| 4438国产精品一区二区| 欧美精品久久天天躁| 天堂在线中文字幕| 欧美福利视频网站| 国产情侣一区二区三区| 国产精品日韩欧美一区二区三区| 欧美日韩性在线观看| 欧美一级视频在线播放| 久色婷婷小香蕉久久| 最近日本中文字幕| 亚洲蜜臀av乱码久久精品 | 7777精品伊人久久久大香线蕉完整版 | 日韩欧美精品一区二区| 狠狠色综合网| 香蕉视频xxxx| 国产精品久久福利| 亚洲大尺度在线观看| 亚洲白虎美女被爆操| 日本电影在线观看网站| 欧美亚洲国产另类| 成人在线视频中文字幕| 致1999电视剧免费观看策驰影院| 亚洲综合精品四区| 日本一区二区免费视频| 亚洲欧洲性图库| 99成人精品视频| 精品香蕉一区二区三区| 俄罗斯一级**毛片在线播放 | 国产精品一区二区人人爽| 亚洲视频在线播放| 日本在线高清| 国产一区二区免费在线观看| 欧美精品午夜| 极品人妻一区二区| 亚洲欧美电影院| 国产乱码精品一区二区| 色老头一区二区三区| 精品免费av在线| 日韩精品电影网站| 日欧美一区二区| 国产人妻一区二区| 91高清视频在线| 国产最新视频在线| 国产精品久久久久久久久久ktv| 久久99免费视频| 92看片淫黄大片一级| 91视频.com| 亚洲第一精品在线观看| 精品一区二区三区三区| 五月天国产在线| 欧美精品v日韩精品v国产精品| 国产深夜精品| 88久久精品无码一区二区毛片| www.国产黄色| 欧美一区二区黄色| 在线观看h网| 成人9ⅰ免费影视网站| 国产综合亚洲精品一区二| 无码国产精品久久一区免费| 亚洲一区国产视频| 无码h黄肉3d动漫在线观看| 97精品视频在线观看| 香蕉久久夜色精品国产更新时间| 欧美精品色婷婷五月综合| 久久亚洲捆绑美女| 中文永久免费观看| 久热精品视频在线观看一区| 综合中文字幕| 国产美女无遮挡网站| 欧美国产精品久久| 国产女人爽到高潮a毛片| 欧美激情视频在线免费观看 欧美视频免费一 | 亚洲国产影院| 亚欧洲乱码视频| 欧美日韩一二三| 91精品久久| 美媛馆国产精品一区二区| 日韩电影一二三区| 日韩视频中文字幕在线观看| 精品国产一区二区亚洲人成毛片| 亚洲天堂手机| 在线码字幕一区| www.激情成人| 人妻中文字幕一区二区三区| 久久综合亚洲社区| 妖精一区二区三区精品视频| 国产成人在线综合| 亚洲成人免费视频| 超碰在线影院| 国产精品一区在线播放| 日本不卡不码高清免费观看 | 色噜噜偷拍精品综合在线| 成人影院在线看| 久久爱av电影| 国内国产精品久久| 69成人免费视频| 超薄丝袜一区二区| 久久99国产精品视频| 成年人看片网站| 欧美偷拍一区二区| 678在线观看视频| 自拍另类欧美| 久久久久9999亚洲精品| www.五月婷|