Spring Boot資料儲存最佳實踐 - Ahad

banq發表於2022-03-21

在這篇文章中,我們回顧了對優化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.。

相關文章