科技新知

Presist and Casecade

前次的文章,講了一些Spring Data最基本的關聯概念,但當要正式儲存或刪除,就有些考慮完整性問題。平常我們在處理資料庫的關聯表格時,也需要面Foreign Key的正確性問題。同樣地,Spring Data也有這方面的考量,但它有提份一個很方便的CascadeType選項,可以簡化一些流程。

假設你只能存取Parent Repo,那你需要在Parent中,加入CascadeType.All。當repo.save(parent)時,它就會順多把所有child的也一併進行Save,你也不需要有Child Repo的存在。

@OneToMany(mappedBy="parent", cascade = CascadeType.All)
List children = newArrayList<>();

但在複雜的狀況下,例如你不想在更新parent的情況下,不小心弄到child,特別是經過public web下的API操作,你對web client的資料正確性有存疑,就不要使用CascadeType了。這也是筆者認為在大多數情況下,我們都會把Parent和Child的CRUD分開操作,然後根據需要使用各自的repo save。

如果你一定要用CascadeType.ALL (CascadeType.REMOVE),就要再留意刪除的問題。為什麼?因為刪除 parent,其實指的是某個parent不再存在,但不代表child也要一起刪除,child的parent連結可以變為null,也有重新連結其他parent的可能。

如果大家確定需要共同刪除,就可以用CascadeType.ALL 或 CascadeType.REMOVE。

還有一個新的選擇,orphanRemoval = true,也有類似效果。

@OneToMany(mappedBy="parent", cascade = CascadeType.REMOVE)
List children = newArrayList<>();
// or@OneToMany(mappedBy="parent", orphanRemoval = true)
List children = newArrayList<>();
// or@OneToMany(mappedBy="parent", cascade = CascadeType.REMOVE, orphanRemoval = true)
List children = newArrayList<>();

筆者測試過,混著用也是可以的。若大家看過其他教程,可能會覺得orphanRemoval = true 和 CascadeType 總是一起出現,但它們其實是分別操作的。單獨使用orphanRemoval = true,有時候則是為了不會出現無主的child,但這不代表parent和child的想要同步更新。

JPA Entity 的生命週期

Spring Data跟傳統的資料庫Selete,Create,Update,Delete SQL 語句有所不同。也就是這個不同,它的CascadeType比資料庫的Cascade Update和Cascade Delete更強大。

Spring Data 預設其實是使用 jakarta.persistence.EntityManager,每個Entity主要分為四個狀態

  • Transient / New - 不在EntityManager的掌控中
  • Managed - 在EntityManager的掌控中,將會在下次flush時,變成sql create或update statement
  • Detached - 脫離EntityManager的掌控,不受flush影響
  • Removed - 在EntityManager的掌控中,將會在下次flush時,變成sql delete statement

在Spring Data / Jpa 以前,我們若要直接操作Hibernate,經常見到persist, remove的寫法

entityManager.persist(entity);
entityManager.remove(entity);

entityManager.detach(entity);
entityManager.merge(entity);

其實persist就是把處於Transient、Removed的entity,改為Managed。而remove就是把Managed改為Removed。detach,merge也類似,就是Managed,Detached之間互換。

EntityManager最強大的是,它可以讓程序員不需要再為Managed狀態下的entity操心,它會自動判別下次flush,應該create還是update,如果完全沒有改動的,連update也不會執行。

(註,flush和commit也有不同,flush就是從java寫到資料庫中,在資料庫commit前,還可以使用rollback放棄。)

而Spring Data,則是進一步簡化,它把persist改為save,remove改為delete,然後自動選擇flush的時機。

CascadeType

在解釋完Entity 的生命週期後,終於可以回到CascadeType了。這裏的CascadeType不是資料庫的Cascade操作,其實它是指EntityManager的狀態操作是否有傳遞關係。亦即是,persist(parent)時,要不要連同child也一起操作?

我們查看 CasecadeType 的原始碼,就可以發現可以被傳遞的操作共有以下這些

  • PERSIST
  • MERGE
  • REMOVE
  • REFRESH
  • DETACH
  • ALL (以上全部)

這裏的 CasecadeType.PERSIST ,跟資料庫的 Cascade Update 是不一樣的。資料庫裏的 Cascade Update,是指當 Parent 的 Primary Key 有變,對應child的 Foreign Key也一起變。但因為 JPA Entity 的機制, Parent 的 Primary Key 不可以改變,理論上不會發生類似資料庫的 Cascade Update,頂多有 Cascade Delete。 CasecadeType.PERSIST 就像之前述的生命週期解說一樣, 把 parent和 child 一起拉到受管理的狀態。

註: CascadeType.REMOVE有點尷尬,似乎有更特別的使用規範。筆者測試過,在某些情況下,CascadeType.REMOVE無法處理ForeignKey問題,又或者是,刪除的順序不對。詳見 spring boot data deletion

Reference

馬交野


超音鼠大電影3
iDOL
DX - IU CONCERT : THE WINNING
IMAX with Laser 封神第二部:戰火西岐
MX4D 工作細胞
4DX  工作細胞
隔壁的房間
工作細胞
哈哈哈新年喜戲
射雕英雄傳:俠之大者
祥賭必贏
麻雀女王追男仔
臨時決鬥
封神第二部:戰火西岐
完美伴侶
IU CONCERT : THE WINNING 2025 字幕版
《七宗罪》30周年 4K IMAX 版本 限定重映
【我推的孩子】-The Final Act-
【我推的孩子】The Final Act
柏靈頓:秘魯大冒險
柏靈頓:秘魯大冒險英語版
GIDLE WORLD TOUR IDOL IN CINEMAS
IVE The 1st World Tour in Cinema
劇場版「進擊的巨人」完結篇
劇場版 「進擊的巨人」完結篇THE LAST ATTACK
劇場版 「進擊的巨人」完結篇 THE LAST ATTACK
假爸爸
劇場版 「進擊的巨人」完結篇
MX4D 超音鼠大電影3
破。鏡。慾
獅子王:木法沙
超音鼠大電影3
iDOL