跳至主要内容

併發更新策略(樂觀鎖)

框架預設不在 audit base class 放 @Version,因此一般 Entity 採 last-write-wins。當 lost update 會造成財務、庫存、額度、狀態機或長時間 編輯資料的實質損害時,才對該 Entity opt in optimistic locking。

完整契約

Entity

@Version
@Column(nullable = false)
private Long version;
  • 使用 Long;version 是 provider 管理的數值,不是時間點。
  • 不初始化,不提供一般 setter。
  • 應用程式、mapper 與 property-copy 不得設定或遞增 version。

API

  • Read response 回傳目前 version
  • 受保護的 update request 必須回送 client 當初讀到的 expected version。
  • 缺少 expected version 是無效請求,不得悄悄降級為 last-write-wins。
  • 衝突回 409 urn:appfuse:error:optimistic-lock;client 重新載入並讓使用者 決定如何合併,不 blind retry。

Application service

先比較 client expected version,再修改 managed Entity;絕對不要把 client version 寫回 Entity。

@Transactional
public Order update(String id, UpdateOrderRequest request) {
Order order = orderRepository.findById(id)
.orElseThrow(() -> new NotFoundException("Order ${0} not found", id));

if (!Objects.equals(order.getVersion(), request.version())) {
throw new ConflictException("Order was modified; reload and try again");
}

order.changeAddress(request.address());
return order;
}

這裡有兩層保護:

  1. application comparison 擋住「client 讀取後、request 送出前」已發生的更新。
  2. Hibernate flush 的 UPDATE ... WHERE id = ? AND version = ? 擋住 「load / compare 後、transaction commit 前」發生的競態。

第二層才是 authoritative database check。若只比較、不加 @Version, 仍有 check-then-act race;若只加 @Version、不讓 expected version 往返, 則只能保護同一 transaction 內的競態,無法辨識 stale HTTP client。

PATCH 與 mapper

  • version 是 request concurrency token,不是可 patch 的 Entity property。
  • PropertyMap、Bean mapper 與 generic copy 必須排除 version
  • Request DTO 可命名 versionexpectedVersion;進入 service 後只比較。
  • Flush 可在 transaction commit 發生;需要在 service 邊界穩定映射例外時, 明確 flush()

限制

  • JPQL / Criteria bulk update 與 native SQL 會繞過一般 Entity version 行為; 必須明確更新 version 並檢查 affected row count,或不要對受保護資料使用 bulk update。
  • Cascade 更新的每個 mutable Entity 都有自己的 concurrency 邊界;不要假設 aggregate root 的 version 自動涵蓋所有獨立 child row。
  • 多租戶隔離與 optimistic locking 正交;tenant predicate 與 version predicate 都必須存在。
  • HTTP ETag / If-Match 可承載同一 expected-version 契約,但不可同時 維護兩套彼此不同步的 token。

速查

問題規範
全域預設last-write-wins
何時加 @Versionsilent overwrite 會造成損害
Client versionread 回傳、update 必填
Service 如何使用與 managed Entity version 比較
可以 setVersion(clientVersion)不可以
最終競態由誰判定Hibernate flush / affected row count
衝突處理409、reload、人工合併;不 blind retry

欄位定義另見 JPA Entity 欄位型別規範