Spring Boot資料儲存最佳實踐 - Ahad
在這篇文章中,我們回顧了對優化spring boot資料訪問層非常有效的最佳實踐。
Spring boot JPA增加了一些關於JPA的介面。JPA只是一種規範,而不是一種實現。有各種實現JPA的ORM,如Hibernate和EclipseLink。
Hibernate提供了許多好處,如物件對映、快取、多事務、獨立於資料庫的查詢等。但是它必須管理每個實體的生命週期(暫存、管理、分離、移除),這導致了一點開銷。但是,如果缺乏足夠的知識來使用它,就會造成嚴重的效能問題,並可能導致產品失敗。
對於優化基於Spring boot JPA和Hibernate的應用程式資料訪問層,我推薦這些技巧:
監測和測試
- 為了實現生產和測試之間100%的資料庫相容性,使用測試容器而不是記憶體資料庫(H2,Fongo)進行測試。
- 使用單元測試來斷言和計算SQL語句,確保你的程式碼不會產生額外的查詢。使用此連結獲取更多資訊。
- 通過在配置檔案中新增這一行來記錄緩慢的查詢(在Hibernate 5.4.5以上版本中可用),否則使用第三方庫,如datasource-proxy。
事務
在事務中,我們應該儘可能地使用只讀和小型事務。下面的說明可以幫助我們提高事務的效能。
- 使用這些配置來推遲獲取連線,直到真正需要的時候。
spring.datasource.hikari.auto-commit=false spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true |
- 使用@Transactional(readOnly=true)來載入只讀狀態的實體。
- @Transactional() 在讀寫狀態下載入實體。只讀狀態要比讀寫狀態便宜得多。
- 不要在私有或保護方法上使用@Transactional()。因為它在這種模式下不起作用。
- 在Repository Interfaces中使用@Transactional(readOnly=true),在有更新或建立操作的服務方法中使用@Transactional()(或任何需要作為單一事務執行的方法)。
- 不要在服務或控制器類中使用 @Transactional() 。
- 重構長時間的事務方法以減少事務執行時間。把它們分解成更小的事務。
Id生成型別
- 不要使用GenerationType.AUTO,因為在Mysql中它對映為GenerationType.TABLE,當你想插入一條新行時,會導致三次查詢。
- GenerationType.SEQUENCE是一個不錯的選擇。但是,如果底層的DBMS不能支援它,請使用GenerationType.IDENTITY(在Mysql中)。
- 不要使用複合鍵和長鍵作為主鍵。避免使用UUID/GUID作為主鍵,因為它是隨機的,會導致B樹的重新組織,最後會降低效能。
關聯和關係
我們應該防止建立意外的表和執行更多的SQL語句。請考慮這些。
- 使用雙向的@OneToMany關聯(1⇆m)而不是單向的(1→m)。
- 使用@JoinColumn可以為單向的@OneToMany提供好處,但是雙向的@OneToMany仍然比它好。
- 單向的@ManyToOne是完全有效的。
- 總是使用從父方到子方的級聯。
- 在@OneToMany中,當在多個會話中載入實體或處理分離的實體時,覆蓋equals()和hashCode()方法。
- 在任何關係型別中,對關聯的每個部分使用懶惰獲取(例如,@ManyToOne(fetch = FetchType.LAZY))
- 在@ManyToMany中選擇關係的所有者,並使用Set而不是List。
讀取和查詢
- 當你不想改變請求的實體時,使用DTO來對映獲取的只讀實體。
- 當想要修改一個實體並需要載入關係的子端時,在JPQL查詢中使用JOIN Fetch,而不是急於載入。如果我們想獲取左邊的所有實體,那麼我們使用LEFT JOIN FETCH。
- 在JPQL/SQL中,JOIN對映到INNER JOIN,LEFT/RIGHT/FULL JOIN等同於LEFT/RIGHT/FULL OUTER JOIN。
- 顯式 JOIN 比隱式 JOIN 好。在某些情況下,隱式JOIN對映為CROSS JOIN而不是INNER JOIN。
- 在父子實體查詢中,當使用JOIN FETCH時,結果可能包含物件引用重複。它為每一條要被獲取的子行重複了父記錄。DISTINCT關鍵字應該只在從JDBC獲得ResultSet後應用,因此建議使用INT_PASS_DISTINCT_THROUGH而不是DISTINCT。不要在標量查詢中使用該方法。
- 使用Spring的內建方法findById()、find()或get()來獲取實體的id,比通過特定的JPQL/SQL查詢來獲取要好。這些方法使用了Hibernate的快取機制(一級快取->二級快取->資料庫),但是JPQL/SQL查詢直接從資料庫中獲取。
- 對於獲取實體,我們可以使用Spring內建的方法,findById()(它使用EntityManagerfind()並返回精確的宣告模型)或getOne()(它使用EntityManagergetReference()並返回Hibernate特定的代理物件)。
- 在@ManyToOne或@OneToOne關聯中,當我們需要用對其父實體的引用來持久化一個子實體時,最好使用getOne()來獲取父方。
- 在沖洗的時候,Hibernate會在實體中找到修改過的屬性,並根據髒檢查機制來更新它們。在更新任務中不要使用save()方法,因為它是一個重複的任務,會導致額外的Hibernate特定內部操作(MergeEvent)。當然,使用save()方法也不會產生額外的查詢。
- 為了避免N+1的問題(執行1個查詢來檢索父實體和N個查詢來檢索子實體),使用JOIN FETCH或實體圖。
- 總是使用分頁來使結果集儘可能的小。
- Open Session In View(OSIV)試圖通過將JPA EntityManager繫結到請求執行緒生命週期中來避免LazyInitializationException。這是一個糟糕的方法和反模式,業務層應該決定獲取的方法。它還會導致效能下降。使用這個配置來關閉OSIV:spring.jpa.open-in-view=false
- 快取執行計劃通過快取準備好的執行計劃來減少負載。該計劃可以重複使用,沒有優化的開銷。如果RDBMS沒有快取執行計劃(Mysql),那麼通過IN子句的引數填充進行快取只會增加語句的複雜性,並可能降低查詢的速度。
- 如果你有大量的查詢,改變Hibernate查詢計劃快取(QPC)以覆蓋你的應用程式中的所有查詢。JPQL和Criteria API的預設大小為2048,本地查詢的預設大小為128。
spring.jpa.properties.hibernate.query.plan_cache_max_size = 3000 spring.jpa.properties.hibernate.query.plan_parameter_metadata_max_size=200 |
- 在oneToMany關聯中,如果你分離一個實體,更新它並儲存它,那麼這個save()方法會導致一個額外的選擇連線查詢。為了避免這種情況,並通過更新優化合並操作,請使用Hibernate會話update()方法而不是直接使用JPA save()方法。
val session=entityManager.unwrap(Session::class.java) session.update(yourEntity) |
永續性上下文
- 要在查詢執行後清除持久化上下文,請使用@Modifying(clearAutomatically = true)。它可能會刪除未儲存的更改。為了防止刪除持久化上下文中的非重新整理實體,使用@Modifying(flushAutomatically = true)。
- 持久化上下文是一個位於應用程式和資料庫之間的第一級快取。Hibernate使用它來處理實體的生命週期。對於檢查持久化上下文中實體的狀態,這段小程式碼很有幫助(Kotlin程式碼)。
@PersistenceContext private val entityManager: EntityManager? = null Val persistenceContext= entityManager.unwrap(SharedSessionContractImplementor::class.java).persistenceContext |
元素集合
- 使用@ElementCollection與嵌入式或基本型別而不是實體。在這種情況下,任何對嵌入物件的粗暴操作都會被直接限制,你需要在父類那邊做這些操作。
- 當你在集合上有大量的插入/刪除操作時,不要使用@ElementCollection。它會導致Hibernate執行大量不需要的插入/刪除操作。
分頁和Hibernate型別
- 使用強大的過濾(keyset分頁)而不是使用偏移分頁。偏移方法在大型資料庫中會降低效能,因為要獲取和丟棄很多行。
- 當你需要Hibernate ORM核心不支援的額外型別(如json)時,請使用Hibernate Types資源庫repository.。
相關文章
- Spring Boot實戰系列(2)資料儲存之NoSQL資料庫MongoDBSpring BootSQL資料庫MongoDB
- Spring Boot 揭祕與實戰(二) 資料儲存篇 – MongoDBSpring BootMongoDB
- Spring Boot 揭祕與實戰(二) 資料儲存篇 – MySQLSpring BootMySql
- Spring Boot 揭祕與實戰(二) 資料儲存篇 - ElasticSearchSpring BootElasticsearch
- Spring Boot 揭祕與實戰(二) 資料儲存篇 - MySQLSpring BootMySql
- Spring Boot 揭祕與實戰(二) 資料儲存篇 - RedisSpring BootRedis
- Spring Boot 揭祕與實戰(二) 資料儲存篇 - MongoDBSpring BootMongoDB
- Spring Boot 揭祕與實戰(二) 資料儲存篇 - JPASpring Boot
- 10個Spring Boot效能最佳實踐Spring Boot
- Spring Boot 安全性最佳實踐Spring Boot
- Spring Boot 揭祕與實戰(二) 資料儲存篇 – MyBatis整合Spring BootMyBatis
- Spring Boot 揭祕與實戰(二) 資料儲存篇 - MyBatis整合Spring BootMyBatis
- Spring Boot 最佳實踐(一)快速入門Spring Boot
- MySQL 中儲存時間的最佳實踐MySql
- 雲端儲存安全標準和最佳實踐
- SQL SERVER十大最佳儲存實踐SQLServer
- 《Android和PHP開發最佳實踐》一2.6 Android資料儲存AndroidPHP
- Spring Boot 最佳實踐(三)模板引擎FreeMarker整合Spring Boot
- Spring Boot統一異常處理最佳實踐Spring Boot
- Spring Boot中五個設計模式最佳實踐Spring Boot設計模式
- Spring Boot 編寫 API 的 10條最佳實踐Spring BootAPI
- Spring Boot乾貨系列:(十一)資料儲存篇-Spring Boot整合Mybatis通用Mapper外掛Spring BootMyBatisAPP
- Spring Boot 揭祕與實戰(二) 資料儲存篇 - 宣告式事務管理Spring Boot
- Spring Boot 揭祕與實戰(二) 資料儲存篇 – 資料訪問與多資料來源配置Spring Boot
- Spring Boot 揭祕與實戰(二) 資料儲存篇 - 資料訪問與多資料來源配置Spring Boot
- 小米大資料儲存服務的資料治理實踐大資料
- 大資料儲存平臺之異構儲存實踐深度解讀大資料
- 中小銀行資料倉儲建設 | 最佳實踐
- SaaS 模式雲資料倉儲 MaxCompute 資料安全最佳實踐模式
- Spring Boot:使用Redis儲存技術Spring BootRedis
- DataLeap資料資產實戰:如何實現儲存最佳化?
- 一種KV儲存的GC最佳化實踐GC
- 分散式系統中的資料儲存方案實踐分散式
- Spring Boot整合Spring Cloud Vault進行安全儲存Spring BootCloud
- 迪斯尼樂園詮釋資料倉儲最佳實踐(下)WE
- 迪斯尼樂園詮釋資料倉儲最佳實踐(上)VE
- 從本地到雲端:豆瓣統一的資料儲存實踐
- 大資料開發的儲存技術探索與實踐大資料