異常全文:org.springframework.dao.InvalidDataAccessApiUsageException: Multiple representations of the same entity are being merged。這是 Hibernate 4.2+ / 5.x 引入的實體複製檢測機制觸發的錯誤,通常發生在 **同一個 Session 中存在兩個代表同一數據庫記錄的 Java 實例**,且嘗試合併時產生衝突。
// 典型錯誤代碼模式
@Transactional
public void problematicMethod() {
// 1. 從數據庫加載實體 (Persistent 狀態)
User managedUser = userDao.findById(1L); // Session 緩存中有 ID=1 的 User
// 2. 創建新實例並設置相同 ID (Transient 狀態,但有 ID)
User detachedUser = new User();
detachedUser.setId(1L);
detachedUser.setName("New Name");
// 3. 嘗試合併/保存新實例 -> 拋出異常
// Session 已經管理 ID=1 的 managedUser,現在又要合併另一個 ID=1 的對象
userDao.saveOrUpdate(detachedUser); // 或 entityManager.merge(detachedUser)
}
原則:對於帶 ID 的更新操作,**先從數據庫加載托管實體,再將前端數據拷貝到該實體**,最後無需顯式調用 save/update(脏檢查自動提交)。
@Transactional
public void updateUserCorrect(UserDTO dto) {
// 1. 加載托管實體 (Persistent)
User managed = userDao.findById(dto.getId());
if (managed == null) throw new EntityNotFoundException("User not found: " + dto.getId());
// 2. 拷貝可更新字段 (推薦用 MapStruct / BeanUtils / 手動 set)
managed.setName(dto.getName());
managed.setEmail(dto.getEmail());
// managed.setPassword(...); // 敏感字段單獨處理
// 3. 無需顯式 save/update,事務提交時自動脏檢查 flush
}
優勢:完全避免雙實例衝突,符合 Hibernate 托管實體語義,性能最優(僅一條 SELECT + 一條 UPDATE)。
在 Hibernate 配置中設置 `hibernate.event.merge.entity_copy_observer=allow`,降級為日誌警告而非拋異常。僅作為臨時兼容舊代碼過渡,可能導致數據不一致風險(最後合併者覆蓋先前變更)。
<!-- Spring XML 配置 LocalSessionFactoryBean -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="hibernateProperties">
<props>
<!-- 關鍵配置 -->
<prop key="hibernate.event.merge.entity_copy_observer">allow</prop>
<!-- 其他屬性... -->
</props>
</property>
</bean>
# Spring Boot application.properties / yml spring.jpa.properties.hibernate.event.merge.entity_copy_observer=allow
若業務邏輯確需在同一事務中替換實例(極少見),可在合併新實例前,將舊實例從 Session 緩存中剔除。
@Transactional
public void replaceEntity(User newUser) {
Session session = sessionFactory.getCurrentSession();
// 1. 若 Session 中已有同 ID 實體,先剔除
User existing = (User) session.get(User.class, newUser.getId());
if (existing != null) {
session.evict(existing); // 變為 Detached 狀態,不再受 Session 管理
}
// 2. 現在可以安全合併新實例
session.merge(newUser); // 或 saveOrUpdate
}
風險:若 `existing` 已有未 flush 的修改,`evict` 會導致修改丟失。慎用。
「Multiple representations of the same entity」本質是 Hibernate 保護 Session 一級緩存唯一性的防線。 根治之道是**尊重托管實體生命週期**:更新操作務必先加載托管實體,再進行字段拷貝。配置 `allow` 僅是權宜之計,長期使用會埋下數據一致性隱患。建立團隊統一的「更新模板代碼」並通過 Code Review 強制執行,是杜絕此類異常的最有效手段。