現象:`$.post` 請求 Struts2 Action,伺服器邏輯正常執行、資料庫查詢無誤,**但瀏覽器 Network 面板無回應、success/error 回調皆不觸發、無 JS 錯誤**。根因:**jQuery 解析回應 JSON 時觸發序列化錯誤,導致整個 AJAX 處理管線中斷且靜默失敗**。
核心原因鏈
- Action 返回 `Collection`,`Privilege` 含 `Set`,`User` 又含 `Set` → **雙向關聯循環引用**
- Spring 事務在 Action 返回前**已提交/關閉** (Open Session In View 未生效或配置錯誤)
- JSON 序列化器 (Jackson/Gson) 遍歷物件圖時,嘗試存取 `Privilege.users` → 觸發 Hibernate Lazy Loading → **無 Session 異常**
- 異常發生在 **序列化階段** (View 層),非 Action 執行階段,Struts2 不會進入 `exception` 拦截器
- jQuery 收到**斷裂/非 JSON 回應** (或空回應),解析拋錯,**但錯誤被 jQuery 內部捕獲並靜默**,`error` 回調不觸發 (因 HTTP 狀態碼 200 OK)
三種解決方案 (按推薦順序)
方案一:JSON 序列化忽略循環引用屬性 (最快、無侵入)
// Privilege 實體類
@JsonIgnoreProperties({"users"}) // Jackson
// 或
@JsonIgnore
public Set getUsers() { return users; }
// Gson 則需
@Expose(serialize = false) transient Set users;
序列化時直接跳過 `users` 集合,避免觸發 Lazy Loading 與循環。
方案二:Service 層預先初始化關聯集合 (根治 Lazy Loading)
@Transactional(readOnly = true)
public List findAllWithUsers() {
List list = privilegeDao.findAll();
list.forEach(p -> Hibernate.initialize(p.getUsers())); // 強制載入
return list;
}
在事務邊界內完成初始化,View 層序列化時無需再查庫。
方案三:啟用 Open Session In View (OSIV) / Open EntityManager In View
openSessionInViewFilter
org.springframework.orm.hibernate5.support.OpenSessionInViewFilter
openSessionInViewFilter
/*
將 Session 生命週期延伸至 View 渲染結束,**但可能導致 N+1 查詢、連線洩漏、事務邊界模糊**,生產環境謹慎使用。
診斷技巧:如何確認是序列化問題
- 瀏覽器 Network → 查看該請求 **Response** 標籤:若顯示「failed」或截斷 JSON,即為序列化中斷
- 伺服器日誌搜尋 `JsonMappingException`、`LazyInitializationException`、`could not initialize proxy`
- 臨時在 Action 中 `return null` 或返回簡單字串,若 AJAX 回調正常執行 → 確認是回應資料問題
最佳實踐總結
- **DTO 模式**:Action 返回專用 DTO (無循環、無 Lazy、扁平結構),而非領域實體
- **@JsonView / @JsonIgnore** 精細控制序列化輸出
- **全局異常處理**:配置 `HandlerExceptionResolver` 捕獲序列化異常並返回標準錯誤 JSON,確保 `error` 回調可觸發
- **關閉 OSIV**,明確在 Service 層 `@Transactional` 內完成資料裝配