国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
25 | 經(jīng)典的 N+1 SQL 問題如何正確解決?(上)

在 JPA 的使用過程中,N+1 SQL 是很常見的問題,相信很多程序員都遇到過這一問題,我看見很多同事處理起來束手無策,那么它究竟真的有那么麻煩嗎?這一講我會幫助你梳理思路,看看到底如何解決這個經(jīng)典問題。

注:由于內(nèi)容較多,我將這部分內(nèi)容拆分成兩講,方便你學(xué)習(xí)。

什么是 N+1 SQL 問題?

想要解決一個問題,必須要知道它是什么、如何產(chǎn)生的,這樣才能有方法、有邏輯地去解決它。下面通過一個例子來看一下什么是 N+1 的 SQL 問題。

假設(shè)一個 UserInfo 實(shí)體對象和 Address 是一對多的關(guān)系,即一個用戶有多個地址,我們首先看一下一般實(shí)體里面的關(guān)聯(lián)關(guān)系會怎么寫。兩個實(shí)體對象如下述代碼所示。

復(fù)制代碼
  1. //UserInfo實(shí)體對象如下:
  2. @Entity
  3. @Data
  4. @SuperBuilder
  5. @AllArgsConstructor
  6. @NoArgsConstructor
  7. @Table
  8. @ToString(exclude = "addressList")//exclued防止 toString打印日志的時候死循環(huán)
  9. public class UserInfo extends BaseEntity {
  10. private String name;
  11. private String telephone;
  12. // UserInfo實(shí)體對象的關(guān)聯(lián)關(guān)系由Address對象里面的userInfo字段維護(hù),默認(rèn)是lazy加載模式,為了方便演示fetch取EAGER模式。此處是一對多關(guān)聯(lián)關(guān)系
  13. @OneToMany(mappedBy = "userInfo",fetch = FetchType.EAGER)
  14. private List<Address> addressList;
  15. }
  16. //Address對象如下:
  17. @Entity
  18. @Table
  19. @Data
  20. @SuperBuilder
  21. @AllArgsConstructor
  22. @NoArgsConstructor
  23. @ToString(exclude = "userInfo")
  24. public class Address extends BaseEntity {
  25. private String city;
  26. //維護(hù)UserInfo和Address的外鍵關(guān)系,方便演示也采用EAGER模式;
  27. @ManyToOne(fetch = FetchType.EAGER)
  28. @JsonBackReference //此注解防止JSON死循環(huán)
  29. private UserInfo userInfo;
  30. }

其次,我們假設(shè)數(shù)據(jù)庫里面有三條 UserInfo 的數(shù)據(jù),ID 分別為 3、6、9,如下圖所示。

其中,每個 UserInfo 分別有兩條 Address 數(shù)據(jù),也就是一共 6 條 Address 的數(shù)據(jù),如下圖所示。

然后,我們請求通過 UserInfoRepository 查詢所有的 UserInfo 信息,方法如下面這行代碼所示。

復(fù)制代碼
  1. userInfoRepository.findAll()

現(xiàn)在,我們的控制臺將會得到四個 SQL,如下所示。

復(fù)制代碼
  1. org.hibernate.SQL :
  2. select userinfo0_.id as id1_1_,
  3. userinfo0_.create_time as create_t2_1_,
  4. userinfo0_.create_user_id as create_u3_1_,
  5. userinfo0_.last_modified_time as last_mod4_1_,
  6. userinfo0_.last_modified_user_id as last_mod5_1_,
  7. userinfo0_.version as version6_1_,
  8. userinfo0_.ages as ages7_1_,
  9. userinfo0_.email_address as email_ad8_1_,
  10. userinfo0_.last_name as last_nam9_1_,
  11. userinfo0_.name as name10_1_,
  12. userinfo0_.telephone as telepho11_1_
  13. from user_info userinfo0_ org.hibernate.SQL :
  14. select addresslis0_.user_info_id as user_inf8_0_0_,
  15. addresslis0_.id as id1_0_0_,
  16. addresslis0_.id as id1_0_1_,
  17. addresslis0_.create_time as create_t2_0_1_,
  18. addresslis0_.create_user_id as create_u3_0_1_,
  19. addresslis0_.last_modified_time as last_mod4_0_1_,
  20. addresslis0_.last_modified_user_id as last_mod5_0_1_,
  21. addresslis0_.version as version6_0_1_,
  22. addresslis0_.city as city7_0_1_,
  23. addresslis0_.user_info_id as user_inf8_0_1_
  24. from address addresslis0_
  25. where addresslis0_.user_info_id = ? org.hibernate.SQL :
  26. select addresslis0_.user_info_id as user_inf8_0_0_,
  27. addresslis0_.id as id1_0_0_,
  28. addresslis0_.id as id1_0_1_,
  29. addresslis0_.create_time as create_t2_0_1_,
  30. addresslis0_.create_user_id as create_u3_0_1_,
  31. addresslis0_.last_modified_time as last_mod4_0_1_,
  32. addresslis0_.last_modified_user_id as last_mod5_0_1_,
  33. addresslis0_.version as version6_0_1_,
  34. addresslis0_.city as city7_0_1_,
  35. addresslis0_.user_info_id as user_inf8_0_1_
  36. from address addresslis0_
  37. where addresslis0_.user_info_id = ? org.hibernate.SQL :
  38. select addresslis0_.user_info_id as user_inf8_0_0_,
  39. addresslis0_.id as id1_0_0_,
  40. addresslis0_.id as id1_0_1_,
  41. addresslis0_.create_time as create_t2_0_1_,
  42. addresslis0_.create_user_id as create_u3_0_1_,
  43. addresslis0_.last_modified_time as last_mod4_0_1_,
  44. addresslis0_.last_modified_user_id as last_mod5_0_1_,
  45. addresslis0_.version as version6_0_1_,
  46. addresslis0_.city as city7_0_1_,
  47. addresslis0_.user_info_id as user_inf8_0_1_
  48. from address addresslis0_
  49. where addresslis0_.user_info_id = ?

通過 SQL 我們可以看得出來,當(dāng)取 UserInfo 的時候,有多少條 UserInfo 數(shù)據(jù)就會觸發(fā)多少條查詢 Address 的 SQL。

那么所謂的 N+1 的 SQL,此時 1 代表的是一條 SQL 查詢 UserInfo 信息;N 條 SQL 查詢 Address 的信息。你可以想象一下,如果有 100 條 UserInfo 信息,可能會觸發(fā) 100 條查詢 Address 的 SQL,性能多差呀。

很簡單,這就是我們常說的 N+1 SQL 問題。我們這里使用的是 EAGER 模式,當(dāng)使用 LAZY 的時候也是一樣的道理,只是生成 N 條 SQL 的時機(jī)是不一樣的。

上面我演示了 @OneToMany 的情況,那么我們再看一下 @ManyToOne 的情況。利用 AddressRepository 查詢所有的 Address 信息,方法如下面這行代碼所示。

復(fù)制代碼
  1. addressRepository.findAll();

這個時候我們再看一下控制臺,會產(chǎn)生如下 SQL。

復(fù)制代碼
  1. org.hibernate.SQL :
  2. select address0_.id as id1_0_,
  3. address0_.create_time as create_t2_0_,
  4. address0_.create_user_id as create_u3_0_,
  5. address0_.last_modified_time as last_mod4_0_,
  6. address0_.last_modified_user_id as last_mod5_0_,
  7. address0_.version as version6_0_,
  8. address0_.city as city7_0_,
  9. address0_.user_info_id as user_inf8_0_
  10. from address address0_
  11. org.hibernate.SQL :
  12. select userinfo0_.id as id1_1_0_,
  13. userinfo0_.create_time as create_t2_1_0_,
  14. userinfo0_.create_user_id as create_u3_1_0_,
  15. userinfo0_.last_modified_time as last_mod4_1_0_,
  16. userinfo0_.last_modified_user_id as last_mod5_1_0_,
  17. userinfo0_.version as version6_1_0_,
  18. userinfo0_.ages as ages7_1_0_,
  19. userinfo0_.email_address as email_ad8_1_0_,
  20. userinfo0_.last_name as last_nam9_1_0_,
  21. userinfo0_.name as name10_1_0_,
  22. userinfo0_.telephone as telepho11_1_0_,
  23. addresslis1_.user_info_id as user_inf8_0_1_,
  24. addresslis1_.id as id1_0_1_,
  25. addresslis1_.id as id1_0_2_,
  26. addresslis1_.create_time as create_t2_0_2_,
  27. addresslis1_.create_user_id as create_u3_0_2_,
  28. addresslis1_.last_modified_time as last_mod4_0_2_,
  29. addresslis1_.last_modified_user_id as last_mod5_0_2_,
  30. addresslis1_.version as version6_0_2_,
  31. addresslis1_.city as city7_0_2_,
  32. addresslis1_.user_info_id as user_inf8_0_2_
  33. from user_info userinfo0_
  34. left outer join address addresslis1_ on userinfo0_.id = addresslis1_.user_info_id
  35. where userinfo0_.id = ?
  36. org.hibernate.SQL :
  37. select userinfo0_.id as id1_1_0_,
  38. userinfo0_.create_time as create_t2_1_0_,
  39. userinfo0_.create_user_id as create_u3_1_0_,
  40. userinfo0_.last_modified_time as last_mod4_1_0_,
  41. userinfo0_.last_modified_user_id as last_mod5_1_0_,
  42. userinfo0_.version as version6_1_0_,
  43. userinfo0_.ages as ages7_1_0_,
  44. userinfo0_.email_address as email_ad8_1_0_,
  45. userinfo0_.last_name as last_nam9_1_0_,
  46. userinfo0_.name as name10_1_0_,
  47. userinfo0_.telephone as telepho11_1_0_,
  48. addresslis1_.user_info_id as user_inf8_0_1_,
  49. addresslis1_.id as id1_0_1_,
  50. addresslis1_.id as id1_0_2_,
  51. addresslis1_.create_time as create_t2_0_2_,
  52. addresslis1_.create_user_id as create_u3_0_2_,
  53. addresslis1_.last_modified_time as last_mod4_0_2_,
  54. addresslis1_.last_modified_user_id as last_mod5_0_2_,
  55. addresslis1_.version as version6_0_2_,
  56. addresslis1_.city as city7_0_2_,
  57. addresslis1_.user_info_id as user_inf8_0_2_
  58. from user_info userinfo0_
  59. left outer join address addresslis1_ on userinfo0_.id = addresslis1_.user_info_id
  60. where userinfo0_.id = ?
  61. org.hibernate.SQL :
  62. select userinfo0_.id as id1_1_0_,
  63. userinfo0_.create_time as create_t2_1_0_,
  64. userinfo0_.create_user_id as create_u3_1_0_,
  65. userinfo0_.last_modified_time as last_mod4_1_0_,
  66. userinfo0_.last_modified_user_id as last_mod5_1_0_,
  67. userinfo0_.version as version6_1_0_,
  68. userinfo0_.ages as ages7_1_0_,
  69. userinfo0_.email_address as email_ad8_1_0_,
  70. userinfo0_.last_name as last_nam9_1_0_,
  71. userinfo0_.name as name10_1_0_,
  72. userinfo0_.telephone as telepho11_1_0_,
  73. addresslis1_.user_info_id as user_inf8_0_1_,
  74. addresslis1_.id as id1_0_1_,
  75. addresslis1_.id as id1_0_2_,
  76. addresslis1_.create_time as create_t2_0_2_,
  77. addresslis1_.create_user_id as create_u3_0_2_,
  78. addresslis1_.last_modified_time as last_mod4_0_2_,
  79. addresslis1_.last_modified_user_id as last_mod5_0_2_,
  80. addresslis1_.version as version6_0_2_,
  81. addresslis1_.city as city7_0_2_,
  82. addresslis1_.user_info_id as user_inf8_0_2_
  83. from user_info userinfo0_
  84. left outer join address addresslis1_ on userinfo0_.id = addresslis1_.user_info_id
  85. where userinfo0_.id = ?

這里通過 SQL 我們可以看得出來,當(dāng)取 Address 的時候,Address 里面有多少個 user_info_id,就會觸發(fā)多少條查詢 UserInfo 的 SQL。

那么所謂的 N+1 的 SQL,此時 1 就代表一條 SQL 查詢 Address 信息;N 條 SQL 查詢 UserInfo 的信息。同樣,你可以想象一下,如果我們有 100 條 Address 信息,分別有不同的 user_info_id 可能會觸發(fā) 100 條查詢 UserInfo 的 SQL,性能依然很差。

這也是我們常說的 N+1 SQL 問題,我只是給你演示了 @OneToMany 和 @ManyToOne 的情況,@ManyToMany 和 @OneToOne 也是一樣的道理,都是當(dāng)我們查詢主體信息時候,1 條 SQL 會衍生出來關(guān)聯(lián)關(guān)系的 N 條 SQL。

現(xiàn)在你認(rèn)識了這個問題,下一步該思考,怎么解決才更合理呢?有沒有什么辦法可以減少 SQL 條數(shù)呢?

減少 N+1 SQL 的條數(shù)

最容易想到,就是有沒有什么機(jī)制可以減少 N 對應(yīng)的 SQL 條數(shù)呢?從原理分析會知道,不管是 LAZY 還是 EAGER 都是沒有用的,因?yàn)檫@兩個只是決定了 N 條 SQL 的觸發(fā)時機(jī),而不能減少 SQL 的條數(shù)。

不知道你是否還記得在第 20 講(Spring JPA 中的 Hibernate 加載過程與配置項(xiàng)是怎么回事)中,我們介紹過的 Hibernate 的配置項(xiàng)有哪些,如果你回過頭去看,會發(fā)現(xiàn)有個配置可以改變每次批量取數(shù)據(jù)的大小。

hibernate.default_batch_fetch_size 配置

hibernate.default_batch_fetch_size 配置在 AvailableSettings.class 里面,指的是批量獲取數(shù)據(jù)的大小,默認(rèn)是 -1,表示默認(rèn)沒有匹配取數(shù)據(jù)。那么我們把這個值改成 20 看一下效果,只需要在 application.properties 里面增加如下配置即可。

復(fù)制代碼
  1. # 更改批量取數(shù)據(jù)的大小為20
  2. spring.jpa.properties.hibernate.default_batch_fetch_size= 20

在實(shí)體類不發(fā)生任何改變的前提下,我們再執(zhí)行如下兩個方法,分別看一下 SQL 的生成情況。

復(fù)制代碼
  1. userInfoRepository.findAll();

還是先查詢所有的 UserInfo 信息,看一下 SQL 的執(zhí)行情況,代碼如下所示。

復(fù)制代碼
  1. org.hibernate.SQL :
  2. select userinfo0_.id as id1_1_,
  3. userinfo0_.create_time as create_t2_1_,
  4. userinfo0_.create_user_id as create_u3_1_,
  5. userinfo0_.last_modified_time as last_mod4_1_,
  6. userinfo0_.last_modified_user_id as last_mod5_1_,
  7. userinfo0_.version as version6_1_,
  8. userinfo0_.ages as ages7_1_,
  9. userinfo0_.email_address as email_ad8_1_,
  10. userinfo0_.last_name as last_nam9_1_,
  11. userinfo0_.name as name10_1_,
  12. userinfo0_.telephone as telepho11_1_
  13. from user_info userinfo0_ org.hibernate.SQL :
  14. select addresslis0_.user_info_id as user_inf8_0_1_,
  15. addresslis0_.id as id1_0_1_,
  16. addresslis0_.id as id1_0_0_,
  17. addresslis0_.create_time as create_t2_0_0_,
  18. addresslis0_.create_user_id as create_u3_0_0_,
  19. addresslis0_.last_modified_time as last_mod4_0_0_,
  20. addresslis0_.last_modified_user_id as last_mod5_0_0_,
  21. addresslis0_.version as version6_0_0_,
  22. addresslis0_.city as city7_0_0_,
  23. addresslis0_.user_info_id as user_inf8_0_0_
  24. from address addresslis0_
  25. where addresslis0_.user_info_id in (?, ?, ?)

我們可以看到 SQL 直接減少到兩條了,其中查詢 Address 的地方查詢條件變成了 in(?,?,?)。

想象一下,如果我們有 20 條 UserInfo 信息,那么產(chǎn)生的 SQL 也是兩條,此時要比 20+1 條 SQL 性能高太多了。

接著我們再執(zhí)行另一個方法,看一下 @ManyToOne 的情況,代碼如下所示。

復(fù)制代碼
  1. addressRepository.findAll()

關(guān)于執(zhí)行的 SQL 情況如下所示。

復(fù)制代碼
  1. 2020-11-29 23:11:27.381 DEBUG 30870 --- [nio-8087-exec-5] org.hibernate.SQL                        :
  2. select address0_.id as id1_0_,
  3. address0_.create_time as create_t2_0_,
  4. address0_.create_user_id as create_u3_0_,
  5. address0_.last_modified_time as last_mod4_0_,
  6. address0_.last_modified_user_id as last_mod5_0_,
  7. address0_.version as version6_0_,
  8. address0_.city as city7_0_,
  9. address0_.user_info_id as user_inf8_0_
  10. from address address0_
  11. 2020-11-29 23:11:27.383 DEBUG 30870 --- [nio-8087-exec-5] org.hibernate.SQL                        :
  12. select userinfo0_.id as id1_1_0_,
  13. userinfo0_.create_time as create_t2_1_0_,
  14. userinfo0_.create_user_id as create_u3_1_0_,
  15. userinfo0_.last_modified_time as last_mod4_1_0_,
  16. userinfo0_.last_modified_user_id as last_mod5_1_0_,
  17. userinfo0_.version as version6_1_0_,
  18. userinfo0_.ages as ages7_1_0_,
  19. userinfo0_.email_address as email_ad8_1_0_,
  20. userinfo0_.last_name as last_nam9_1_0_,
  21. userinfo0_.name as name10_1_0_,
  22. userinfo0_.telephone as telepho11_1_0_,
  23. addresslis1_.user_info_id as user_inf8_0_1_,
  24. addresslis1_.id as id1_0_1_,
  25. addresslis1_.id as id1_0_2_,
  26. addresslis1_.create_time as create_t2_0_2_,
  27. addresslis1_.create_user_id as create_u3_0_2_,
  28. addresslis1_.last_modified_time as last_mod4_0_2_,
  29. addresslis1_.last_modified_user_id as last_mod5_0_2_,
  30. addresslis1_.version as version6_0_2_,
  31. addresslis1_.city as city7_0_2_,
  32. addresslis1_.user_info_id as user_inf8_0_2_
  33. from user_info userinfo0_
  34. left outer join address addresslis1_ on userinfo0_.id = addresslis1_.user_info_id
  35. where userinfo0_.id in (?, ?, ?)

從代碼中可以看到,我們查詢所有的 Address 信息也只產(chǎn)生了 2 條 SQL;而當(dāng)我們查詢 UserInfo 的時候,SQL 最后的查詢條件也變成了 in (?,?,?),同樣的道理這樣也會提升不少性能。

而 hibernate.default_batch_fetch_size 的經(jīng)驗(yàn)參考值,可以設(shè)置成 20、30、50、100 等,太高了也沒有意義。一個請求執(zhí)行一次,產(chǎn)生的 SQL 數(shù)量為 3-5 條基本上都算合理情況,這樣通過設(shè)置 default_batch_fetch_size 就可以很好地避免大部分業(yè)務(wù)場景下的 N+1 條 SQL 的性能問題了。

此時你還需要注意一點(diǎn)就是,在實(shí)際工作中,一定要知道我們一次操作會產(chǎn)生多少 SQL,有沒有預(yù)期之外的 SQL 參數(shù),這是需要關(guān)注的重點(diǎn),這種情況可以利用我們之前說過的如下配置來開啟打印 SQL,請看代碼。

復(fù)制代碼
  1. ## 顯示sql的執(zhí)行日志,如果開了這個,show_sql就可以不用了,show_sql沒有上下文,多線程情況下,分不清楚是誰打印的,所有我推薦如下配置項(xiàng):
  2. logging.level.org.hibernate.SQL=debug

但是這種配置也有個缺陷,就是只能全局配置,沒辦法針對不通過的實(shí)體管理關(guān)系配置不同的 Fetch Size 的值。

而與之類似的 Hibernate 里面也提供了一個注解 @BatchSize 可以解決此問題。

@BatchSize 注解

@BatchSize 注解是 Hibernate 提供的用來解決查詢關(guān)聯(lián)關(guān)系的批量處理大小,默認(rèn)無,可以配置在實(shí)體上,也可以配置在關(guān)聯(lián)關(guān)系上面。此注解里面只有一個屬性 size,用來指定關(guān)聯(lián)關(guān)系 LAZY 或者是 EAGER 一次性取數(shù)據(jù)的大小。

我們還是將上面的例子中的 UserInfo 實(shí)體做一下改造,在里面增加兩次 @BatchSize 注解,代碼如下所示。

復(fù)制代碼
  1. @Entity
  2. @Data
  3. @SuperBuilder
  4. @AllArgsConstructor
  5. @NoArgsConstructor
  6. @Table
  7. @ToString(exclude = "addressList")
  8. @BatchSize(size = 2)//實(shí)體類上加@BatchSize注解,用來設(shè)置當(dāng)被關(guān)聯(lián)關(guān)系的時候一次查詢的大小,我們設(shè)置成2,方便演示Address關(guān)聯(lián)UserInfo的時候的效果
  9. public class UserInfo extends BaseEntity {
  10. private String name;
  11. private String telephone;
  12. @OneToMany(mappedBy = "userInfo",cascade = CascadeType.PERSIST,fetch = FetchType.EAGER)
  13. @BatchSize(size = 20)//關(guān)聯(lián)關(guān)系的屬性上加@BatchSize注解,用來設(shè)置當(dāng)通過UserInfo加載Address的時候一次取數(shù)據(jù)的大小
  14. private List<Address> addressList;
  15. }

我們通過改造 UserInfo 實(shí)體,可以直接演示 @BatchSize 應(yīng)用在實(shí)體類和屬性字段上的效果,所以 Address 實(shí)體可以不做任何改變,hibernate.default_batch_fetch_size 還改成默認(rèn)值 -1,我們再分別執(zhí)行一下兩個 findAll 方法,看一下效果。

第一種:查詢所有 UserInfo,代碼如下面這行所示。

復(fù)制代碼
  1. userInfoRepository.findAll()

我們看一下 SQL 控制臺。

復(fù)制代碼
  1.  org.hibernate.SQL :
  2. select userinfo0_.id as id1_1_,
  3. userinfo0_.create_time as create_t2_1_,
  4. userinfo0_.create_user_id as create_u3_1_,
  5. userinfo0_.last_modified_time as last_mod4_1_,
  6. userinfo0_.last_modified_user_id as last_mod5_1_,
  7. userinfo0_.version as version6_1_,
  8. userinfo0_.ages as ages7_1_,
  9. userinfo0_.email_address as email_ad8_1_,
  10. userinfo0_.last_name as last_nam9_1_,
  11. userinfo0_.name as name10_1_,
  12. userinfo0_.telephone as telepho11_1_
  13. from user_info userinfo0_ org.hibernate.SQL :
  14. select addresslis0_.user_info_id as user_inf8_0_1_,
  15. addresslis0_.id as id1_0_1_,
  16. addresslis0_.id as id1_0_0_,
  17. addresslis0_.create_time as create_t2_0_0_,
  18. addresslis0_.create_user_id as create_u3_0_0_,
  19. addresslis0_.last_modified_time as last_mod4_0_0_,
  20. addresslis0_.last_modified_user_id as last_mod5_0_0_,
  21. addresslis0_.version as version6_0_0_,
  22. addresslis0_.city as city7_0_0_,
  23. addresslis0_.user_info_id as user_inf8_0_0_
  24. from address addresslis0_
  25. where addresslis0_.user_info_id in (?, ?, ?)

和剛才設(shè)置 hibernate.default_batch_fetch_size=20 的效果一模一樣,所以我們可以利用 @BatchSize 這個注解針對不同的關(guān)聯(lián)關(guān)系,配置不同的大小,從而提升 N+1 SQL 的性能。

第二種:查詢一下所有 Address,如下面這行代碼所示。

復(fù)制代碼
  1. addressRepository.findAll();

我們看一下控制臺的 SQL 情況,如下所示。

復(fù)制代碼
  1. org.hibernate.SQL :
  2. select address0_.id as id1_0_,
  3. address0_.create_time as create_t2_0_,
  4. address0_.create_user_id as create_u3_0_,
  5. address0_.last_modified_time as last_mod4_0_,
  6. address0_.last_modified_user_id as last_mod5_0_,
  7. address0_.version as version6_0_,
  8. address0_.city as city7_0_,
  9. address0_.user_info_id as user_inf8_0_
  10. from address address0_
  11. org.hibernate.SQL :
  12. select userinfo0_.id as id1_1_0_,
  13. userinfo0_.create_time as create_t2_1_0_,
  14. userinfo0_.create_user_id as create_u3_1_0_,
  15. userinfo0_.last_modified_time as last_mod4_1_0_,
  16. userinfo0_.last_modified_user_id as last_mod5_1_0_,
  17. userinfo0_.version as version6_1_0_,
  18. userinfo0_.ages as ages7_1_0_,
  19. userinfo0_.email_address as email_ad8_1_0_,
  20. userinfo0_.last_name as last_nam9_1_0_,
  21. userinfo0_.name as name10_1_0_,
  22. userinfo0_.telephone as telepho11_1_0_,
  23. addresslis1_.user_info_id as user_inf8_0_1_,
  24. addresslis1_.id as id1_0_1_,
  25. addresslis1_.id as id1_0_2_,
  26. addresslis1_.create_time as create_t2_0_2_,
  27. addresslis1_.create_user_id as create_u3_0_2_,
  28. addresslis1_.last_modified_time as last_mod4_0_2_,
  29. addresslis1_.last_modified_user_id as last_mod5_0_2_,
  30. addresslis1_.version as version6_0_2_,
  31. addresslis1_.city as city7_0_2_,
  32. addresslis1_.user_info_id as user_inf8_0_2_
  33. from user_info userinfo0_
  34. left outer join address addresslis1_ on userinfo0_.id = addresslis1_.user_info_id
  35. where userinfo0_.id in (?, ?)
  36. org.hibernate.SQL :
  37. select userinfo0_.id as id1_1_0_,
  38. userinfo0_.create_time as create_t2_1_0_,
  39. userinfo0_.create_user_id as create_u3_1_0_,
  40. userinfo0_.last_modified_time as last_mod4_1_0_,
  41. userinfo0_.last_modified_user_id as last_mod5_1_0_,
  42. userinfo0_.version as version6_1_0_,
  43. userinfo0_.ages as ages7_1_0_,
  44. userinfo0_.email_address as email_ad8_1_0_,
  45. userinfo0_.last_name as last_nam9_1_0_,
  46. userinfo0_.name as name10_1_0_,
  47. userinfo0_.telephone as telepho11_1_0_,
  48. addresslis1_.user_info_id as user_inf8_0_1_,
  49. addresslis1_.id as id1_0_1_,
  50. addresslis1_.id as id1_0_2_,
  51. addresslis1_.create_time as create_t2_0_2_,
  52. addresslis1_.create_user_id as create_u3_0_2_,
  53. addresslis1_.last_modified_time as last_mod4_0_2_,
  54. addresslis1_.last_modified_user_id as last_mod5_0_2_,
  55. addresslis1_.version as version6_0_2_,
  56. addresslis1_.city as city7_0_2_,
  57. addresslis1_.user_info_id as user_inf8_0_2_
  58. from user_info userinfo0_
  59. left outer join address addresslis1_ on userinfo0_.id = addresslis1_.user_info_id
  60. where userinfo0_.id = ?

這里可以看到,由于我們在 UserInfo 的實(shí)體上設(shè)置了 @BatchSize(size = 2),表示所有關(guān)聯(lián)關(guān)系到 UserInfo 的時候一次取兩條數(shù)據(jù),所以就會發(fā)現(xiàn)這次我查詢 Address 加載 UserInfo 的時候,產(chǎn)生了 3 條 SQL。

其中通過關(guān)聯(lián)關(guān)系查詢 UserInfo 產(chǎn)生了 2 條 SQL,由于我們 UserInfo 在數(shù)據(jù)庫里面有三條數(shù)據(jù),所以第一條 UserInfo 的 SQL 受 @BatchSize(size = 2) 控制,從而 in (?,?) 只支持了兩個參數(shù),同時也產(chǎn)生了第二條查 UserInfo 的 SQL。

從上面的例子中我們可以看到 @BatchSize 和 hibernate.default_batch_fetch_size 的效果是一樣的,只不過一個是全局配置、一個是局部設(shè)置,這是可以減少 N+1 SQL 最直接、最方便的兩種方式。

注意事項(xiàng):

@BatchSize 的使用具有局限性,不能作用于 @ManyToOne 和 @OneToOne 的關(guān)聯(lián)關(guān)系上,那樣代碼是不起作用的,如下所示。

復(fù)制代碼
  1. public class Address extends BaseEntity {
  2. private String city;
  3. @ManyToOne(cascade = CascadeType.PERSIST,fetch = FetchType.EAGER)
  4. @BatchSize(size = 30) //由于是@ManyToOne的關(guān)聯(lián)關(guān)系所有沒有作用
  5. private UserInfo userInfo;
  6. }

因此,你要注意 @BatchSize 只能作用在 @ManyToMany、@OneToMany、實(shí)體類這三個地方。
此外,Hibernate 中還提供了一種 FetchMode 的策略,包含三種模式,分別為 FetchMode.SELECT、FetchMode.JOIN,以及 FetchMode.Subselect。由于內(nèi)容較多,我怕你一次性不好消化,所以會在下一講繼續(xù)為你介紹。到時見。

點(diǎn)擊下方鏈接查看源碼(不定時更新)
https://github.com/zhangzhenhuajack/spring-boot-guide/tree/master/spring-data/spring-data-jpa

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
hibernate 自增 IDENTITY
oracle建立自動增長字段
學(xué)習(xí)Java6(六) ---嵌入式數(shù)據(jù)庫 Derby
(轉(zhuǎn))hibernate常用API詳解
iBatis 到 MyBatis區(qū)別
ibatis 到 MyBatis區(qū)別
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服