spring-data-jpa.html 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <meta name="generator" content="Asciidoctor 2.0.15">
  8. <meta name="author" content="pxzxj, pudge.zxj@gmail.com, 2022/04/26">
  9. <title>Spring Data JPA最佳实践</title>
  10. <link rel="stylesheet" href="css/site.css">
  11. <link href="css/custom.css" rel="stylesheet">
  12. <script src="js/setup.js"></script><script defer src="js/site.js"></script>
  13. </head>
  14. <body class="article toc2 toc-left"><div id="banner-container" class="container" role="banner">
  15. <div id="banner" class="contained" role="banner">
  16. <div id="switch-theme">
  17. <input type="checkbox" id="switch-theme-checkbox" />
  18. <label for="switch-theme-checkbox">Dark Theme</label>
  19. </div>
  20. </div>
  21. </div>
  22. <div id="tocbar-container" class="container" role="navigation">
  23. <div id="tocbar" class="contained" role="navigation">
  24. <button id="toggle-toc"></button>
  25. </div>
  26. </div>
  27. <div id="main-container" class="container">
  28. <div id="main" class="contained">
  29. <div id="doc" class="doc">
  30. <div id="header">
  31. <h1>Spring Data JPA最佳实践</h1>
  32. <div class="details">
  33. <span id="author" class="author">pxzxj</span><br>
  34. <span id="author2" class="author">pudge.zxj@gmail.com</span><br>
  35. <span id="author3" class="author">2022/04/26</span><br>
  36. </div>
  37. <div id="toc" class="toc2">
  38. <div id="toctitle">Table of Contents</div>
  39. <span id="back-to-index"><a href="index.html">Back to index</a></span><ul class="sectlevel1">
  40. <li><a href="#_introduction">1. Introduction</a></li>
  41. <li><a href="#_identify_issues">2. Identify Issues</a></li>
  42. <li><a href="#_association_fetching">3. Association Fetching</a></li>
  43. <li><a href="#_many_to_many_association">4. Many-to-Many Association</a></li>
  44. <li><a href="#_projection">5. Projection</a>
  45. <ul class="sectlevel2">
  46. <li><a href="#_projection_with_entity">5.1. Projection with entity</a></li>
  47. <li><a href="#_projection_with_interface">5.2. Projection with interface</a></li>
  48. <li><a href="#_nested_associations">5.3. Nested associations</a></li>
  49. <li><a href="#_spel">5.4. SPEL</a></li>
  50. </ul>
  51. </li>
  52. </ul>
  53. </div>
  54. </div>
  55. <div id="content">
  56. <div id="preamble">
  57. <div class="sectionbody">
  58. <div class="paragraph">
  59. <p>本文根据SpringOne 2021的演讲Building Fast and Scalable Persistence Layers with Spring Data JPA整理而成,英语能力不错的建议直接观看下面的原始视频</p>
  60. </div>
  61. <div class="videoblock"><div class="content">
  62. <iframe width="640" height="480" src="https://player.bilibili.com/player.html?bvid=BV1Rq4y167qQ&high_quality=1&page=1" border="0" frameborder="no" framespacing="0" scrolling="no" allowfullscreen="true"></iframe>
  63. </div></div>
  64. <div class="paragraph">
  65. <p>演讲的PPT和源码在下面的地址</p>
  66. </div>
  67. <div class="ulist">
  68. <ul>
  69. <li>
  70. <p><a href="https://thorben-janssen.com/wp-content/uploads/talks/Fast%20and%20Scalable%20Persistence%20Layers%20with%20Spring%20Data%20JPA.pdf">Slides</a></p>
  71. </li>
  72. <li>
  73. <p><a href="https://github.com/thjanssen/Talk-ScalablePersistenceLayersSpringDataJPA">Source Code</a></p>
  74. </li>
  75. </ul>
  76. </div>
  77. <div class="admonitionblock tip">
  78. <table>
  79. <tr>
  80. <td class="icon">
  81. <i class="fa icon-tip" title="Tip"></i>
  82. </td>
  83. <td class="content">
  84. 演讲中的示例代码使用了postgresql数据库,本地没有安装的可以使用
  85. <code>docker run --name some-postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=test -p 5432:5432 --rm postgres</code> 启动一个postgresql容器
  86. </td>
  87. </tr>
  88. </table>
  89. </div>
  90. </div>
  91. </div>
  92. <div class="sect1">
  93. <h2 id="_introduction"><a class="anchor" href="#_introduction"></a>1. Introduction</h2>
  94. <div class="sectionbody">
  95. <div class="paragraph">
  96. <p>Spring Data JPA是Spring Data家族的其中一个项目,主要对一些实现了JPA规范的持久层框架进行封装和增强,
  97. 它极大地降低了JPA的使用难度,使那些基于Spring框架构建的应用非常轻松地实现持久层访问。</p>
  98. </div>
  99. <div class="paragraph">
  100. <p>Spring Data JPA良好的封装性使它非常易用,但也隐藏了内部所有的执行逻辑,使其成为一个黑盒子,当然对于一般的中小型应用也确实不需要关注
  101. 其内部的执行逻辑,但是在构建大型应用或者对性能要求较高的中小型应用时很可能会发现Spring Data JPA并不能满足要求,
  102. 这通常是由于使用方法不当使用导致的,因此本文对Spring Data JPA的最佳实践进行说明,遵循这些建议可以在
  103. 提升开发效率的同时保证应用运行速度。</p>
  104. </div>
  105. </div>
  106. </div>
  107. <div class="sect1">
  108. <h2 id="_identify_issues"><a class="anchor" href="#_identify_issues"></a>2. Identify Issues</h2>
  109. <div class="sectionbody">
  110. <div class="paragraph">
  111. <p>要提升Spring Data JPA的执行效率,首先需要能够观测其内部执行情况,
  112. 大部分情况下Spring Data JPA与Hibernate一起使用,因此可以通过配置Hibernate的相关参数在控制台展示内部执行情况。</p>
  113. </div>
  114. <div class="listingblock primary">
  115. <div class="title">Properties</div>
  116. <div class="content">
  117. <pre class="highlight"><code class="language-properties" data-lang="properties">spring.jpa.properties.hibernate[generate_statistics]=true
  118. spring.jpa.properties.hibernate.session.events.log[LOG_QUERIES_SLOWER_THAN_MS]=3
  119. logging.level.org.hibernate.stat=debug
  120. logging.level.org.hibernate[SQL]=debug</code></pre>
  121. </div>
  122. </div>
  123. <div class="listingblock secondary">
  124. <div class="title">Yaml</div>
  125. <div class="content">
  126. <pre class="highlight"><code class="language-yaml" data-lang="yaml">spring:
  127. jpa:
  128. properties:
  129. hibernate:
  130. generate_statistics: true # <1>
  131. session:
  132. events:
  133. log:
  134. LOG_QUERIES_SLOWER_THAN_MS: 3 # <2>
  135. logging:
  136. level:
  137. org:
  138. hibernate:
  139. stat: debug # <3>
  140. SQL: debug</code></pre>
  141. </div>
  142. </div>
  143. <div class="colist arabic">
  144. <table>
  145. <tr>
  146. <td><i class="conum" data-value="1"></i><b>1</b></td>
  147. <td>输出执行统计</td>
  148. </tr>
  149. <tr>
  150. <td><i class="conum" data-value="2"></i><b>2</b></td>
  151. <td>输出慢Sql</td>
  152. </tr>
  153. <tr>
  154. <td><i class="conum" data-value="3"></i><b>3</b></td>
  155. <td>输出执行的Sql</td>
  156. </tr>
  157. </table>
  158. </div>
  159. <div class="admonitionblock note">
  160. <table>
  161. <tr>
  162. <td class="icon">
  163. <i class="fa icon-note" title="Note"></i>
  164. </td>
  165. <td class="content">
  166. 注意这些参数配置会一定程度上影响执行速度,因此生产环境不建议使用
  167. </td>
  168. </tr>
  169. </table>
  170. </div>
  171. <div class="paragraph">
  172. <p>修改 <code>ChessPlayer#tournaments</code> 的 fetchType 为 <code>EAGER</code> 并运行 <code>TestDemo#findAll()</code> 会在控制台输出执行的SQL以及如下统计信息</p>
  173. </div>
  174. <div class="listingblock">
  175. <div class="content">
  176. <pre>2022-04-25 21:45:13.095 INFO 30756 --- [ main] i.StatisticalLoggingSessionEventListener : Session Metrics {
  177. 2150100 nanoseconds spent acquiring 1 JDBC connections;
  178. 0 nanoseconds spent releasing 0 JDBC connections;
  179. 1206300 nanoseconds spent preparing 20 JDBC statements;
  180. 45058100 nanoseconds spent executing 20 JDBC statements;
  181. 0 nanoseconds spent executing 0 JDBC batches;
  182. 92742400 nanoseconds spent performing 40 L2C puts;
  183. 0 nanoseconds spent performing 0 L2C hits;
  184. 729500 nanoseconds spent performing 19 L2C misses;
  185. 0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
  186. 13700 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections)
  187. }</pre>
  188. </div>
  189. </div>
  190. </div>
  191. </div>
  192. <div class="sect1">
  193. <h2 id="_association_fetching"><a class="anchor" href="#_association_fetching"></a>3. Association Fetching</h2>
  194. <div class="sectionbody">
  195. <div class="paragraph">
  196. <p>关联查询是JPA中非常常见的操作,定义实体关联关系的注解都包含了一个 <code>fetch</code> 属性用于定义关联查询方式</p>
  197. </div>
  198. <div class="listingblock">
  199. <div class="content">
  200. <pre class="highlight"><code class="language-java" data-lang="java">@ManyToOne(fetch = FetchType.LAZY)
  201. private Publisher publisher;
  202. </code></pre>
  203. </div>
  204. </div>
  205. <div class="paragraph">
  206. <p><code>FetchType</code> 是一个枚举类型,包含 <code>LAZY</code> 和 <code>EAGER</code> 两个值表示使用时查询和立即查询,
  207. ToMany默认使用 <code>LAZY</code>,ToOne默认使用 <code>EAGER</code>。</p>
  208. </div>
  209. <div class="paragraph">
  210. <p><code>EAGER</code> 意味着不管是否使用都会查询关联的实体,因此一般情况下 <code>LAZY</code> 是更好的选择,也就是说
  211. ToOne需要显式指定 <code>fetch</code> 值为 <code>LAZY</code>。而在可以确定后续代码中会使用关联的实体的场景中则
  212. 可以使用 <code>@Query</code> 注解手动在JPQL语句中指定join关系用于同时查询所需实体和其关联实体。</p>
  213. </div>
  214. <div class="paragraph">
  215. <p><code>TestDemo#findWithTournaments()</code> 用于演示 LEFT JOIN FETCH 和 EntityGraph 的效果</p>
  216. </div>
  217. <div class="olist arabic">
  218. <ol class="arabic">
  219. <li>
  220. <p>修改 <code>ChessPlayer#tournaments</code> 的 <code>fetch</code> 值为默认值。</p>
  221. </li>
  222. <li>
  223. <p>注释 <code>ChessPlayerRepository#findWithTournamentsBy()</code> 的 @Query 和 @EntityGraph 后执行
  224. <code>TestDemo#findWithTournaments()</code>,观察控制台统计信息发现会执行20条statement。</p>
  225. </li>
  226. <li>
  227. <p>分别打开 <code>ChessPlayerRepository#findWithTournamentsBy()</code> 的 @Query 和 @EntityGraph 注释后再
  228. 执行 <code>TestDemo#findWithTournaments()</code>,观察控制台统计信息发现都只执行一条statement。</p>
  229. </li>
  230. </ol>
  231. </div>
  232. <div class="admonitionblock note">
  233. <table>
  234. <tr>
  235. <td class="icon">
  236. <i class="fa icon-note" title="Note"></i>
  237. </td>
  238. <td class="content">
  239. 使用 <code>join</code> 查询确实会减少执行的sql数量,但也意味着会一次查询大量数据,因此很容易出现慢sql,尤其是
  240. 关联多个实体时,因此在使用时要特别注意。
  241. </td>
  242. </tr>
  243. </table>
  244. </div>
  245. </div>
  246. </div>
  247. <div class="sect1">
  248. <h2 id="_many_to_many_association"><a class="anchor" href="#_many_to_many_association"></a>4. Many-to-Many Association</h2>
  249. <div class="sectionbody">
  250. <div class="paragraph">
  251. <p>多对多关联的场景中,我们经常使用 <code>List</code> 保存 <strong>多</strong> 的实体,然而此时Hibernate在对这些 <strong>多</strong> 的实体执行写操作时效率很低,
  252. 必须使用 <code>Set</code> 进行替换。</p>
  253. </div>
  254. <div class="paragraph">
  255. <p><code>TestDemo#removePlayerFromTournament()</code> 用于演示使用 <code>List</code> 和 <code>Set</code> 时执行不同的sql</p>
  256. </div>
  257. <div class="olist arabic">
  258. <ol class="arabic">
  259. <li>
  260. <p>首先修改 <code>ChessTournament#players</code> 的类型为List并执行 <code>TestDemo#removePlayerFromTournament()</code> 观察控制台信息会发现执行了
  261. 18条statement,因为先删除了所有的player再插入不需要删除的player。</p>
  262. </li>
  263. <li>
  264. <p>修改 <code>ChessTournament#players</code> 的类型为Set再次执行 <code>TestDemo#removePlayerFromTournament()</code> 观察控制台信息会发现
  265. 执行了4条statement。</p>
  266. </li>
  267. </ol>
  268. </div>
  269. <div class="listingblock">
  270. <div class="title">List</div>
  271. <div class="content">
  272. <pre>2022-04-26 20:30:50.183 DEBUG 19764 --- [ main] org.hibernate.SQL :
  273. select
  274. chesstourn0_.id as id1_2_0_,
  275. chesstourn0_.end_date as end_date2_2_0_,
  276. chesstourn0_.name as name3_2_0_,
  277. chesstourn0_.start_date as start_da4_2_0_,
  278. chesstourn0_.version as version5_2_0_
  279. from
  280. chess_tournament chesstourn0_
  281. where
  282. chesstourn0_.id=?
  283. 2022-04-26 20:30:50.214 INFO 19764 --- [ main] org.hibernate.SQL_SLOW : SlowQuery: 6 milliseconds. SQL: 'HikariProxyPreparedStatement@472019958 wrapping select chesstourn0_.id as id1_2_0_, chesstourn0_.end_date as end_date2_2_0_, chesstourn0_.name as name3_2_0_, chesstourn0_.start_date as start_da4_2_0_, chesstourn0_.version as version5_2_0_ from chess_tournament chesstourn0_ where chesstourn0_.id=2'
  284. 2022-04-26 20:30:50.386 DEBUG 19764 --- [ main] org.hibernate.SQL :
  285. select
  286. players0_.tournaments_id as tourname1_4_0_,
  287. players0_.players_id as players_2_4_0_,
  288. chessplaye1_.id as id1_1_1_,
  289. chessplaye1_.birth_date as birth_da2_1_1_,
  290. chessplaye1_.first_name as first_na3_1_1_,
  291. chessplaye1_.last_name as last_nam4_1_1_,
  292. chessplaye1_.version as version5_1_1_
  293. from
  294. chess_tournament_players players0_
  295. inner join
  296. chess_player chessplaye1_
  297. on players0_.players_id=chessplaye1_.id
  298. where
  299. players0_.tournaments_id=?
  300. 2022-04-26 20:30:50.406 INFO 19764 --- [ main] org.hibernate.SQL_SLOW : SlowQuery: 4 milliseconds. SQL: 'HikariProxyPreparedStatement@1453157901 wrapping select players0_.tournaments_id as tourname1_4_0_, players0_.players_id as players_2_4_0_, chessplaye1_.id as id1_1_1_, chessplaye1_.birth_date as birth_da2_1_1_, chessplaye1_.first_name as first_na3_1_1_, chessplaye1_.last_name as last_nam4_1_1_, chessplaye1_.version as version5_1_1_ from chess_tournament_players players0_ inner join chess_player chessplaye1_ on players0_.players_id=chessplaye1_.id where players0_.tournaments_id=2'
  301. 2022-04-26 20:30:50.558 DEBUG 19764 --- [ main] org.hibernate.SQL :
  302. update
  303. chess_tournament
  304. set
  305. end_date=?,
  306. name=?,
  307. start_date=?,
  308. version=?
  309. where
  310. id=?
  311. and version=?
  312. 2022-04-26 20:30:50.583 INFO 19764 --- [ main] org.hibernate.SQL_SLOW : SlowQuery: 5 milliseconds. SQL: 'HikariProxyPreparedStatement@177522915 wrapping update chess_tournament set end_date='2021-05-25 +08', name='Local Championship', start_date='2021-05-22 +08', version=1 where id=2 and version=0'
  313. 2022-04-26 20:30:50.590 DEBUG 19764 --- [ main] org.hibernate.SQL :
  314. delete
  315. from
  316. chess_tournament_players
  317. where
  318. tournaments_id=?
  319. 2022-04-26 20:30:50.594 DEBUG 19764 --- [ main] org.hibernate.SQL :
  320. insert
  321. into
  322. chess_tournament_players
  323. (tournaments_id, players_id)
  324. values
  325. (?, ?)
  326. ...
  327. 2022-04-26 20:30:50.646 DEBUG 19764 --- [ main] org.hibernate.SQL :
  328. insert
  329. into
  330. chess_tournament_players
  331. (tournaments_id, players_id)
  332. values
  333. (?, ?)
  334. 2022-04-26 20:30:50.650 INFO 19764 --- [ main] org.hibernate.SQL_SLOW : SlowQuery: 4 milliseconds. SQL: 'HikariProxyPreparedStatement@306941929 wrapping insert into chess_tournament_players (tournaments_id, players_id) values (2, 33)'
  335. 2022-04-26 20:30:50.669 INFO 19764 --- [ main] i.StatisticalLoggingSessionEventListener : Session Metrics {
  336. 3880100 nanoseconds spent acquiring 1 JDBC connections;
  337. 0 nanoseconds spent releasing 0 JDBC connections;
  338. 2100900 nanoseconds spent preparing 18 JDBC statements;
  339. 58723500 nanoseconds spent executing 18 JDBC statements;
  340. 0 nanoseconds spent executing 0 JDBC batches;
  341. 144689100 nanoseconds spent performing 18 L2C puts;
  342. 0 nanoseconds spent performing 0 L2C hits;
  343. 6642700 nanoseconds spent performing 1 L2C misses;
  344. 149136500 nanoseconds spent executing 1 flushes (flushing a total of 16 entities and 47 collections);
  345. 0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
  346. }</pre>
  347. </div>
  348. </div>
  349. <div class="listingblock">
  350. <div class="title">Set</div>
  351. <div class="content">
  352. <pre>2022-04-26 20:33:32.488 DEBUG 32020 --- [ main] org.hibernate.SQL :
  353. select
  354. chesstourn0_.id as id1_2_0_,
  355. chesstourn0_.end_date as end_date2_2_0_,
  356. chesstourn0_.name as name3_2_0_,
  357. chesstourn0_.start_date as start_da4_2_0_,
  358. chesstourn0_.version as version5_2_0_
  359. from
  360. chess_tournament chesstourn0_
  361. where
  362. chesstourn0_.id=?
  363. 2022-04-26 20:33:32.503 INFO 32020 --- [ main] org.hibernate.SQL_SLOW : SlowQuery: 5 milliseconds. SQL: 'HikariProxyPreparedStatement@622702995 wrapping select chesstourn0_.id as id1_2_0_, chesstourn0_.end_date as end_date2_2_0_, chesstourn0_.name as name3_2_0_, chesstourn0_.start_date as start_da4_2_0_, chesstourn0_.version as version5_2_0_ from chess_tournament chesstourn0_ where chesstourn0_.id=2'
  364. 2022-04-26 20:33:32.611 DEBUG 32020 --- [ main] org.hibernate.SQL :
  365. select
  366. players0_.tournaments_id as tourname1_4_0_,
  367. players0_.players_id as players_2_4_0_,
  368. chessplaye1_.id as id1_1_1_,
  369. chessplaye1_.birth_date as birth_da2_1_1_,
  370. chessplaye1_.first_name as first_na3_1_1_,
  371. chessplaye1_.last_name as last_nam4_1_1_,
  372. chessplaye1_.version as version5_1_1_
  373. from
  374. chess_tournament_players players0_
  375. inner join
  376. chess_player chessplaye1_
  377. on players0_.players_id=chessplaye1_.id
  378. where
  379. players0_.tournaments_id=?
  380. 2022-04-26 20:33:32.789 DEBUG 32020 --- [ main] org.hibernate.SQL :
  381. update
  382. chess_tournament
  383. set
  384. end_date=?,
  385. name=?,
  386. start_date=?,
  387. version=?
  388. where
  389. id=?
  390. and version=?
  391. 2022-04-26 20:33:32.809 INFO 32020 --- [ main] org.hibernate.SQL_SLOW : SlowQuery: 6 milliseconds. SQL: 'HikariProxyPreparedStatement@1761528165 wrapping update chess_tournament set end_date='2021-05-25 +08', name='Local Championship', start_date='2021-05-22 +08', version=1 where id=2 and version=0'
  392. 2022-04-26 20:33:32.818 DEBUG 32020 --- [ main] org.hibernate.SQL :
  393. delete
  394. from
  395. chess_tournament_players
  396. where
  397. tournaments_id=?
  398. and players_id=?
  399. 2022-04-26 20:33:32.836 INFO 32020 --- [ main] i.StatisticalLoggingSessionEventListener : Session Metrics {
  400. 2230700 nanoseconds spent acquiring 1 JDBC connections;
  401. 0 nanoseconds spent releasing 0 JDBC connections;
  402. 1028500 nanoseconds spent preparing 4 JDBC statements;
  403. 17931500 nanoseconds spent executing 4 JDBC statements;
  404. 0 nanoseconds spent executing 0 JDBC batches;
  405. 113258000 nanoseconds spent performing 18 L2C puts;
  406. 0 nanoseconds spent performing 0 L2C hits;
  407. 11600200 nanoseconds spent performing 1 L2C misses;
  408. 116423200 nanoseconds spent executing 1 flushes (flushing a total of 16 entities and 47 collections);
  409. 0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
  410. }</pre>
  411. </div>
  412. </div>
  413. </div>
  414. </div>
  415. <div class="sect1">
  416. <h2 id="_projection"><a class="anchor" href="#_projection"></a>5. Projection</h2>
  417. <div class="sectionbody">
  418. <div class="paragraph">
  419. <p>Spring Data JPA提供的默认查询虽然便于使用,但每次都会查询所有字段,很多时候这是不必要的,
  420. 比如在使用联合索引的情况下,只查询索引包含的字段可以提升查询效率。
  421. 为此Spring Data JPA提供了Projection实现只查询部分字段。</p>
  422. </div>
  423. <div class="sect2">
  424. <h3 id="_projection_with_entity"><a class="anchor" href="#_projection_with_entity"></a>5.1. Projection with entity</h3>
  425. <div class="paragraph">
  426. <p><code>TestDemo#getPlayerNamesDto()</code> 用于演示基于实体类的Projection,从控制台的输出可以看到生成的SQL只查询了实体的两个属性</p>
  427. </div>
  428. <div class="listingblock">
  429. <div class="content">
  430. <pre class="highlight"><code class="language-java" data-lang="java">public class PlayerName {
  431. private String firstName;
  432. private String lastName;
  433. //getter, setter
  434. }
  435. </code></pre>
  436. </div>
  437. </div>
  438. <div class="listingblock">
  439. <div class="content">
  440. <pre>2022-04-26 21:11:25.688 DEBUG 26960 --- [ main] org.hibernate.SQL :
  441. select
  442. chessplaye0_.first_name as col_0_0_,
  443. chessplaye0_.last_name as col_1_0_
  444. from
  445. chess_player chessplaye0_
  446. where
  447. chessplaye0_.first_name=?</pre>
  448. </div>
  449. </div>
  450. </div>
  451. <div class="sect2">
  452. <h3 id="_projection_with_interface"><a class="anchor" href="#_projection_with_interface"></a>5.2. Projection with interface</h3>
  453. <div class="paragraph">
  454. <p><code>TestDemo#getPlayerNames()</code> 用于演示基于接口的Projection,从控制台的输出可以看到生成的sql只查询了接口的get方法对于的字段</p>
  455. </div>
  456. <div class="listingblock">
  457. <div class="content">
  458. <pre class="highlight"><code class="language-java" data-lang="java">public interface PlayerNameIntf {
  459. String getFirstName();
  460. String getLastName();
  461. }
  462. </code></pre>
  463. </div>
  464. </div>
  465. <div class="listingblock">
  466. <div class="content">
  467. <pre>2022-04-26 21:17:52.261 DEBUG 31952 --- [ main] org.hibernate.SQL :
  468. select
  469. chessplaye0_.first_name as col_0_0_,
  470. chessplaye0_.last_name as col_1_0_
  471. from
  472. chess_player chessplaye0_
  473. where
  474. chessplaye0_.first_name=?</pre>
  475. </div>
  476. </div>
  477. <div class="paragraph">
  478. <p>一般情况下优先选择使用基于接口的Projection,除了声明更简单外还支撑Native Query,<code>TestDemo#getPlayerNamesDtoNative()</code>
  479. 和 <code>TestDemo#getPlayerNamesDtoNative()</code> 分别使用实体类和接口接收Native Query的查询结果,
  480. 但使用实体类时会抛出 <code>ConverterNotFoundException</code>,而使用接口则能正常查询。</p>
  481. </div>
  482. </div>
  483. <div class="sect2">
  484. <h3 id="_nested_associations"><a class="anchor" href="#_nested_associations"></a>5.3. Nested associations</h3>
  485. <div class="paragraph">
  486. <p>Projection还支持接口中定义关联的实体,<code>TestDemo#getTournamentWithPlayers()</code> 用于演示此场景</p>
  487. </div>
  488. <div class="listingblock">
  489. <div class="content">
  490. <pre class="highlight"><code class="language-java" data-lang="java">public interface TournamentIntf {
  491. String getName();
  492. List&lt;PlayerNameIntf&gt; getPlayers();
  493. }
  494. public interface PlayerNameIntf {
  495. String getFirstName();
  496. String getLastName();
  497. }
  498. </code></pre>
  499. </div>
  500. </div>
  501. <div class="listingblock">
  502. <div class="content">
  503. <pre>2022-04-26 22:21:20.112 DEBUG 9948 --- [ main] org.hibernate.SQL :
  504. select
  505. chesstourn0_.id as id1_2_,
  506. chesstourn0_.end_date as end_date2_2_,
  507. chesstourn0_.name as name3_2_,
  508. chesstourn0_.start_date as start_da4_2_,
  509. chesstourn0_.version as version5_2_
  510. from
  511. chess_tournament chesstourn0_
  512. where
  513. chesstourn0_.name=?
  514. 2022-04-26 22:21:20.138 INFO 9948 --- [ main] org.hibernate.SQL_SLOW : SlowQuery: 13 milliseconds. SQL: 'HikariProxyPreparedStatement@1965388767 wrapping select chesstourn0_.id as id1_2_, chesstourn0_.end_date as end_date2_2_, chesstourn0_.name as name3_2_, chesstourn0_.start_date as start_da4_2_, chesstourn0_.version as version5_2_ from chess_tournament chesstourn0_ where chesstourn0_.name='Tata Steel Chess Tournament 2021''
  515. 2022-04-26 22:21:20.222 DEBUG 9948 --- [ main] o.h.stat.internal.StatisticsImpl : HHH000117: HQL: select generatedAlias0 from ChessTournament as generatedAlias0 where generatedAlias0.name=:param0, time: 122ms, rows: 1
  516. 2022-04-26 22:21:20.238 INFO 9948 --- [ main] c.thorben.janssen.spring.data.TestDemo : ======== Test Output ===========
  517. 2022-04-26 22:21:20.253 DEBUG 9948 --- [ main] org.hibernate.SQL :
  518. select
  519. players0_.tournaments_id as tourname1_4_0_,
  520. players0_.players_id as players_2_4_0_,
  521. chessplaye1_.id as id1_1_1_,
  522. chessplaye1_.birth_date as birth_da2_1_1_,
  523. chessplaye1_.first_name as first_na3_1_1_,
  524. chessplaye1_.last_name as last_nam4_1_1_,
  525. chessplaye1_.version as version5_1_1_
  526. from
  527. chess_tournament_players players0_
  528. inner join
  529. chess_player chessplaye1_
  530. on players0_.players_id=chessplaye1_.id
  531. where
  532. players0_.tournaments_id=?</pre>
  533. </div>
  534. </div>
  535. <div class="paragraph">
  536. <p>尽管查询能够正常执行,但从控制台的输出可以看出查询了所有字段,而不只是Projection中声明的字段,这样的结果
  537. 完全失去了Projection的优势,反而还多定义了接口,因此绝对不要使用Nested associations。</p>
  538. </div>
  539. </div>
  540. <div class="sect2">
  541. <h3 id="_spel"><a class="anchor" href="#_spel"></a>5.4. SPEL</h3>
  542. <div class="paragraph">
  543. <p>Projection的接口支持使用SPEL表达式,<code>TestDemo#getPlayerFullNames()</code> 演示了这一场景,与
  544. Nested associations一样,最终会查询所有字段,因此这种方式也不要使用。</p>
  545. </div>
  546. <div class="listingblock">
  547. <div class="content">
  548. <pre class="highlight"><code class="language-java" data-lang="java">public interface PlayerFullNameIntf {
  549. @Value("#{target.lastName +', ' + target.firstName}")
  550. String getFullName();
  551. }
  552. </code></pre>
  553. </div>
  554. </div>
  555. <div class="paragraph">
  556. <p>通过在Projection接口中定义默认方法既可以保留Projection的好处,又能对查询的字段计算后返回,
  557. <code>TestDemo#getBetterPlayerFullNames()</code> 演示了这一用法</p>
  558. </div>
  559. <div class="listingblock">
  560. <div class="content">
  561. <pre class="highlight"><code class="language-java" data-lang="java">public interface BetterPlayerFullNameIntf {
  562. String getFirstName();
  563. String getLastName();
  564. default String getFullName() {return getLastName()+", "+getFirstName();}
  565. }
  566. </code></pre>
  567. </div>
  568. </div>
  569. <div class="paragraph">
  570. <p>从控制台的输出也可以看出只查询了get方法对应的字段</p>
  571. </div>
  572. <div class="listingblock">
  573. <div class="content">
  574. <pre>2022-04-26 22:29:44.729 DEBUG 25592 --- [ main] org.hibernate.SQL :
  575. select
  576. chessplaye0_.first_name as col_0_0_,
  577. chessplaye0_.last_name as col_1_0_
  578. from
  579. chess_player chessplaye0_
  580. where
  581. chessplaye0_.first_name=?</pre>
  582. </div>
  583. </div>
  584. </div>
  585. </div>
  586. </div>
  587. </div>
  588. <div id="footer">
  589. <div id="footer-text">
  590. Last updated 2024-03-18 05:44:42 UTC
  591. </div>
  592. </div>
  593. <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/highlight.min.js"></script>
  594. <script>
  595. if (!hljs.initHighlighting.called) {
  596. hljs.initHighlighting.called = true
  597. ;[].slice.call(document.querySelectorAll('pre.highlight > code')).forEach(function (el) { hljs.highlightBlock(el) })
  598. }
  599. </script>
  600. <script src="https://utteranc.es/client.js"
  601. repo="pxzxj/articles"
  602. issue-term="title"
  603. label="utteranc"
  604. theme="github-light"
  605. crossorigin="anonymous"
  606. async>
  607. </script>
  608. </div>
  609. </div>
  610. </div>
  611. </body>
  612. </html>