跳至主要内容

Entity 歷程稽核

Entity 歷程稽核用來回答:「哪一個 changeset 改了哪些 Entity、修改前後是什麼,以及當時的 subject、actor、delegation grantors 是誰?」目前的 reference adapter 使用 Hibernate Envers, 但身份脈絡契約不依賴 Envers。

三個互相獨立的稽核層

層次解決的問題啟用方式是否包含 acting 維度
AuditableBase目前這列由誰建立、最後由誰修改Entity 選擇繼承否,只存有效身份 subject
Entity history每次交易改了什麼、修改前後版本與 changeset metadataapp 安裝 feature,Entity 再加 @Audited是,revision 記 subjectactorgrantors
AuditEvent登入、授權、acting 存取、HTTP 結果等安全/操作事件audit 與各事件發布點分別選配事件 payload 依事件類型決定

三層沒有 prerequisite 關係。安裝 Entity history 不會改寫 AuditableBase,也不會自動 audit 所有 Entity;沒有安裝 impersonation 或 delegation 時,metadata 會自然退化為 subject = actorgrantors = []

身份語意

所有身份欄都保存 stable subject(目前通常是 Account UUID 字串),不是 username 或顯示名稱。 顯示時才透過應用的 principal directory 解析。

情境subjectIdactorIdgrantorIds
一般操作登入者登入者空集合
Impersonation被模擬者真實操作者空集合
Delegation登入者登入者權限授權人集合
兩者並存被模擬者真實操作者權限授權人集合

AuditChangeContext 另外承載 principalKind、可選 tenantIdrequestIdsessionIdsourceSecurityAuditChangeContextProvider 從 Spring Security、ActingContext 與當前 HTTP request 解算這些值;非 HTTP 的已認證工作標為 APPLICATION,沒有 Authentication 的 工作以 system 主體記錄(非 HTTP 來源為 SYSTEM,HTTP 來源仍為 HTTP)。 外部 request/correlation ID 超過 reference schema 的 100 字元上限時,provider 會保存固定長度 SHA-256 摘要,避免不受信任標頭使 audit transaction 寫入失敗。

啟用方式

Entity history 是 application-level optional feature。reference app 的 gradle/features/entity-audit.gradle.kts 才加入 runtime adapter 相依:

dependencies {
add("implementation", "org.hibernate.orm:hibernate-envers")
}

框架的 Envers adapter 是 compileOnly,所以沒有安裝此 feature 的應用不會被迫帶入 Envers。 移除 feature 時,應一併移除 feature catalog 宣告、Gradle seam、revision entity/listener 與 相關測試支援。

安裝 feature 後,仍須逐一選擇要保留歷程的 Entity:

@Entity
@Audited
public class PurchaseOrder extends AuditableBase {
// ...
}

沒有 @Audited 的 production Entity 不會建立 _AUD 歷程表,也不會因 feature 存在而被納入。

框架與應用的責任

框架提供兩層窄契約:

  • AuditChangeContextAuditChangeContextProviderAuditChangeContextAware:與 history engine 無關的 changeset metadata。
  • EnversAuditRevisionListener:把上述 context 寫入 application-owned revision entity 的 Envers adapter。

應用擁有具體 @RevisionEntity、資料表、索引、欄位長度與 tenant 投影。tenant reference app 用 listener 子類注入 TenantContext::getTenantIdOrNull;tenantless variant 使用中立 provider, 其 audit_revision 不含 tenant_id

Reference schema 使用:

  • audit_revision:一筆代表同一個持久化 transaction 的 changeset,保存 subject、actor、 principal kind、request/session/source,以及 tenant app 的 tenant。
  • audit_revision_grantor:以 (REV, grantor_order) 保存 0..n 個授權人,不把多值身份塞進 JSON 或逗號字串。
  • <entity>_AUD:Envers 保存 Entity 各版本,透過 REV 指回 changeset metadata。

同一個 transaction 修改多個 audited Entity 時只產生一筆 revision;因此 actor/grantors 是 changeset metadata,不需要重複塞進每個業務 Entity 或 AuditableBase

查詢與交易邊界

Revision 在 audited transaction commit 時完成。在同一個尚未提交的 transaction 內,以 AuditReader 查不到該次 revision 是正常行為;查詢與驗證應跨過 commit boundary。

AuditReader reader = AuditReaderFactory.get(entityManager);
List<Number> revisions = reader.getRevisions(PurchaseOrder.class, orderId);
AuditRevision metadata = reader.findRevision(AuditRevision.class, revisions.getLast());

Envers 攔截 Hibernate Entity lifecycle。bulk HQL/JPQL update、native SQL、資料庫端程序或其他 繞過一般 Entity lifecycle 的寫入,不應假設會產生完整歷程;使用前須以實際 persistence 路徑做整合測試,或改由 application service 明確發布另一種 audit record。

Adapter 演進

公開身份契約與應用 schema 不直接依賴 Envers listener API;只有 audit.entity.envers adapter 與 application revision wiring 知道 Envers。升級 Hibernate 或 改用其他 history engine 時,先替換 adapter 並驗證既有 REV 關聯與查詢相容性,不需要把 impersonation/delegation 欄位搬進所有 Entity。