异步处理时的JPA
幾年前,在Java世界中,幾乎所有的“企業(yè)”類項目都需要JPA與數(shù)據(jù)庫進(jìn)行通信,這幾乎是顯而易見的。 JPA是Joel Spolsky描述的“ 泄漏抽象 ”的完美示例。 剛開始時很棒而又容易,但最后很難調(diào)整和限制。 對于許多參與數(shù)據(jù)訪問層的后端開發(fā)人員而言,日常工作是黑客并直接使用緩存,刷新和本機(jī)查詢。 有足夠的問題和變通辦法來寫一本專門的書“面向黑客的JPA”,但是在本文中,我將只關(guān)注并發(fā)實體處理。
讓我們假設(shè)這種情況:我們有一個Person實體,該實體在某些業(yè)務(wù)流程中由某些服務(wù)更新。
@Entity public class Person {@Id@GeneratedValueprivate Long id;private String uuid = UUID.randomUUID().toString();private String firstName;private String lastName;// getters and setters}為了忽略任何域的復(fù)雜性,我們正在談?wù)摳麓巳说拿趾托帐稀?我們可以想象代碼如下:
firstNameUpdater.update(personUuid, "Jerry"); lastNameUpdater.update(personUuid, "Newman");經(jīng)過一段時間的業(yè)務(wù)決定,更新兩個元素都需要花費太長時間,因此縮短工期成為當(dāng)務(wù)之急。 當(dāng)然,有很多不同的方法可以做到這一點,但讓我們假設(shè),這種特殊情況并發(fā)將解決我們的難題。 這似乎很容易-只需使用Spring和voilà中的@Async注釋我們的服務(wù)方法即可解決問題。 真? 根據(jù)樂觀鎖定機(jī)制的使用,我們這里有兩個可能的問題。
- 使用樂觀鎖定,幾乎可以肯定,我們將從其中一種更新方法中獲得OptimisticLockException-一種將排名第二的方法。 與根本不使用樂觀鎖定相比,這種情況更好。
- 沒有版本控制,所有更新將毫無例外地完成,但是從數(shù)據(jù)庫加載更新的實體后,我們將僅發(fā)現(xiàn)一個更改。 為什么會這樣呢? 兩種方法都更新了不同的字段! 為什么第二筆交易覆蓋了其他更新? 由于泄漏的抽象:)
我們知道,Hibernate正在跟蹤對我們實體所做的更改(稱為臟檢查)。 但是為了減少編譯查詢所需的時間,默認(rèn)情況下,它在更新查詢中包括所有字段,而不是僅包含已更改的字段。 看起來很奇怪? 幸運的是,我們可以配置Hibernate以其他方式工作,并根據(jù)實際更改的值生成更新查詢。 可以使用@DynamicUpdate批注啟用它。 這可以被視為解決部分更新問題的方法,但是您必須記住這是一個折衷方案。 現(xiàn)在,此實體的每次更新都比以前更耗時。
現(xiàn)在讓我們回到樂觀鎖定的情況。 老實說,我們想要做的通常與這種鎖定的想法相反,這種鎖定假定可能不會對該實體進(jìn)行任何并發(fā)修改,并且在發(fā)生這種情況時會引發(fā)異常。 現(xiàn)在我們肯定要進(jìn)行并發(fā)修改! 作為一種快速的解決方法,我們可以從鎖定機(jī)制中排除這兩個字段( firstName和lastName )。 可以通過在每個字段上添加@OptimisticLock(excluded = true)來實現(xiàn)。 現(xiàn)在,更新名稱將不會觸發(fā)版本增加-它將保持不變,這當(dāng)然可以成為許多令人討厭且難以發(fā)現(xiàn)一致性問題的來源。
最后但并非最不重要的解決方案是旋轉(zhuǎn)更改。 要使用它,我們必須將更新邏輯包裝在循環(huán)中,并在發(fā)生OptimisticLock時在事務(wù)處理時進(jìn)行更新。 效果越好,進(jìn)程中涉及的線程越少。 所有這些解決方案的源代碼都可以在我的GitHub的jpa-async-examples存儲庫中找到 。 只是探索提交。
等待-仍然沒有適當(dāng)?shù)慕鉀Q方案? 實際上沒有。 僅由于使用了JPA,我們才對并發(fā)修改問題的簡單解決方案不了解。 當(dāng)然,我們可以重塑我們的應(yīng)用程序以引入一些基于事件的方法,但是上面我們?nèi)匀挥蠮PA。 如果我們使用域驅(qū)動設(shè)計,則嘗試通過使用OPTIMISTIC_FORCE_INCREMENT鎖定來關(guān)閉整個聚合,只是要確保更改復(fù)合實體或向集合中添加元素會更新整個聚合,因為它可以保護(hù)不變量。 那么,為什么不使用任何直接訪問工具,例如JOOQ或JdbcTemplate呢? 這個主意很棒,但不幸的是,不能與JPA同時使用。 JOOQ所做的任何修改都不會自動傳播到JPA,這意味著會話或緩存可能包含過時的值。
為了正確解決這種情況,我們應(yīng)該將此上下文提取到單獨的元素中,例如new table,它將直接由JOOQ處理。 您可能已經(jīng)注意到,在SQL中進(jìn)行這種并發(fā)更新非常容易:
update person set first_name = "Jerry" where uuid = ?;使用JPA抽象,它變成了非常復(fù)雜的任務(wù),需要對Hibernate行為以及實現(xiàn)內(nèi)部進(jìn)行真正的深入了解。 綜上所述,我認(rèn)為JPA沒有遵循“反應(yīng)式”方法。 它是為解決某些問題而構(gòu)建的,但是目前我們提出了不同的問題,而在許多應(yīng)用程序中,持久性并不是其中之一。
翻譯自: https://www.javacodegeeks.com/2015/11/jpa-in-case-of-asynchronous-processing.html
總結(jié)
- 上一篇: linux超时退出设置(linux 超时
- 下一篇: Hystrix简介–总结