38

我有一個在web服務器上運行的java項目。我總是遇到這個例外。行被另一個事務更新或刪除(或未保存的值映射不正確)

我讀了一些文檔,發現悲觀鎖定(或樂觀,但我讀過悲觀更好)是防止這種異常的最好方法。

但我找不到任何明確的例子解釋如何使用它。

我的方法是這樣的:

@Transactional 
Public void test(Email email, String Subject){ 
    getEmailById(String id); 
    email.setSubject(Subject); 
    updateEmail(email); 
} 

而:

  • Email是休眠類(這將是在數據庫中的表)
  • getEmailById(String id)是返回email的函數(這種方法不用@Transctional註釋)
  • updateEmail(email):是我更新電子郵件的方法。

注:我用冬眠保存,等等(例如:session.getcurrentSession.save(email))更新&

例外:

ERROR 2011-12-21 15:29:24,910 Could not synchronize database state with session [myScheduler-1] 
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [email#21] 
    at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1792) 
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2435) 
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2335) 
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2635) 
    at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:115) 
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279) 
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263) 
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168) 
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) 
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50) 
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027) 
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365) 
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137) 
    at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:656) 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754) 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723) 
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393) 
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) 
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202) 
    at $Proxy130.generateEmail(Unknown Source) 
    at com.admtel.appserver.tasks.EmailSender.run(EmailNotificationSender.java:33) 
    at com.admtel.appserver.tasks.EmailSender$$FastClassByCGLIB$$ea0d4fc2.invoke(<generated>) 
    at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149) 
    at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:688) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150) 
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:55) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161) 
    at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:50) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161) 
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:50) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161) 
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) 
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:621) 
    at com.admtel.appserver.tasks.EmailNotificationSender$$EnhancerByCGLIB$$33eb7303.run(<generated>) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
    at java.lang.reflect.Method.invoke(Method.java:597) 
    at org.springframework.util.MethodInvoker.invoke(MethodInvoker.java:273) 
    at org.springframework.scheduling.support.MethodInvokingRunnable.run(MethodInvokingRunnable.java:65) 
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:51) 
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441) 
    at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:317) 
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:150) 
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.java:98) 
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.runPeriodic(ScheduledThreadPoolExecutor.java:180) 
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:204) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) 
    at java.lang.Thread.run(Thread.java:680) 
ERROR 2011-12-21 15:29:24,915 [ exception thrown < EmailNotificationSender.run() > exception message Object of class [Email] with identifier [211]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Email#21] with params ] [myScheduler-1] 
org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException: Object of class [Email] with identifier [21]: optimistic locking failed; nested exception is 
+1

您確認數據庫沒有被從多個地方更新,你的代碼是唯一一個這樣做呢?也是你的方法'測試(電子郵件電子郵件,字符串主題)'被同時訪問? – Santosh 2011-12-27 14:34:27

+0

你正在使用哪個數據庫? – Santosh 2011-12-27 14:41:50

+0

我使用postgre數據庫。沒有我的項目仍在測試中,只有在我的電腦中,所以沒有人可以訪問它,我只是誰更新數據庫上的數據,iam創建一個服務,每30秒自動調用一次該方法,該服務是調用此方法的唯一服務 – 2011-12-28 07:05:35

回答

39

通常不推薦使用悲觀鎖定,並且在數據庫方面性能非常高昂。你提到的問題(代碼部分)有一些東西不清楚,如:

  • 如果你的代碼被多個線程同時訪問。
  • 你是如何創建session對象(不知道你是否使用Spring)?

休眠Session objects are NOT thread-safe。因此,如果有多個線程訪問同一會話並嘗試更新相同的數據庫實體,那麼您的代碼可能最終會出現像這樣的錯誤情況。

因此,這裏發生的情況是,多個線程嘗試更新同一個實體,一個線程成功並且下一個線程提交數據時,它看到它已被修改並最終拋出StaleObjectStateException

編輯

還有就是用悲觀鎖在Hibernate中的一種方式。檢查出this link。但是這種機制似乎存在一些問題。然而,我發現在休眠時發佈了一個bug(HHH-5275)。該bug中提到的場景如下:

兩個線程正在讀取同一個數據庫記錄;其中一個線程 應該使用悲觀鎖定,從而阻塞另一個線程。但是 這兩個線程都可以讀取導致測試失敗的數據庫記錄。

這是非常接近你面臨的。請嘗試這個,如果這不起作用,我能想到的唯一方法是使用Native SQL queries,在那裏你可以實現pessimistic locking in postgres數據庫與SELECT FOR UPDATE查詢。

+0

我知道這就是爲什麼我需要鎖定數據庫,直到當前會話完成其更新。我如何鎖定數據庫(樂觀/悲觀)?如果可以,請通過代碼直到我。 iam使用spring,但這是我的項目中的一個小例子。謝謝 – 2011-12-28 06:55:10

+0

Plz檢查我更新的答案。 – Santosh 2011-12-28 14:21:09

+0

好吧,我會盡力並稍後更新您的感謝 – 2011-12-29 07:04:13

11

它不會出現你實際上使用從數據庫中檢索的電子郵件,但使用作爲參數獲取的較舊副本。無論是何時用於版本控制的行都會在檢索先前版本和更新時發生變化。

你可能想你的代碼看起來更像:

@Transactional 
Public void test(String id, String subject){ 
    Email email = getEmailById(id); 
    email.setSubject(subject); 
    updateEmail(email); 
} 
+1

肯定版本是問題。但是這種方法的改變並沒有影響我多次嘗試這個解決方案。如果您有其他解決方案,請將其發佈到 – 2011-12-28 06:58:54

4

此異常可能是由樂觀鎖引起的(或在你的代碼中的bug)。你可能在不知情的情況下使用它。而你的僞代碼(它應該被真正的代碼替代以便能夠診斷問題)是錯誤的。 Hibernate會自動保存對連接實體所做的所有修改。您不應該在連接的實體上調用update,merge或saveOrUpdate。只要做

Email email = session.get(emailId); 
email.setSubject(subject); 

無需調用更新。 Hibernate將在提交事務之前自動刷新更改。

+0

這不是主要問題。更新功能不會導致此錯誤。我想在我的項目中使用樂觀或悲觀鎖定,我如何將它們用作我的場景?謝謝 – 2011-12-28 07:01:42

+1

樂觀鎖定包括檢測另一個事務已更新/刪除同一行,並拋出此例中的異常。如果你有一個版本字段,你已經在使用樂觀鎖定了,​​這就是拋出異常的原因。悲觀鎖定會使您的應用程序變得緩慢並受到死鎖的困擾。處理它:如果多個用戶正在更新相同的行,其中一個用戶會得到異常。這是正常的和預期的。 – 2011-12-28 07:10:36

+0

如果多個用戶更新同一行並且有一個用戶獲得例外,這是不正常的,因爲其他功能中的用戶正在更新餘額。如果一個用戶得到一個例外,那麼餘額不會更新。現在我只是誰訪問這個程序,我得到這個例外,沒有幾個 用戶訪問它,如果許多用戶訪問它會發生什麼。 你知道我該如何使用悲觀鎖 – 2011-12-28 08:04:41

2

檢查對象存在與否的數據庫,如果存在獲取對象和刷新:

if (getEntityManager().contains(instance)) { 
    getEntityManager().refresh(instance); 
    return instance; 
} 

如果它失敗了,如果以上條件......找到ID在DB的對象,做您需要的操作,在這種情況下,準確更改將反映出來。

if (....) { 
    } else if (null != identity) { 
     E dbInstance = (E) getEntityManager().find(instance.getClass(), identity); 
     return dbInstance; 
    } 
4

我知道這是一個古老的問題,但我們中的一些人仍然打它,看着天空徘徊如何。這是我遇到的一種問題,

我們有一個隊列管理器,用於輪詢數據並提供給處理程序進行處理。爲了避免再次撿起相同的事件,隊列管理器將數據庫中的記錄鎖定爲LOCKED狀態。

void poll() { 
     record = dao.getLockedEntity(); 
     queue(record); 
    } 

這種方法是不能交易的,但dao.getLockedEntity()是交易與「必要」。

所有良好和道路上,在生產幾個月後,它與樂觀鎖定異常失敗,

經過大量的調試和詳細檢查,我們可以發現,有一個人已經改變了這樣的代碼,

@Transactional(propagation=Propagation.REQUIRED, readOnly=false) 
void poll() { 
     record = dao.getLockedEntity(); 
     queue(record);    
    } 

所以記錄是即使在dao.getLockedEntity交易之前排隊()得到承諾(它使用輪詢方法相同的交易)和對象是由時間的處理程序(不同的線程)下改變poll()方法事務得到comitted。

我們解決了這個問題,現在看起來不錯。

我想分享它,因爲樂觀鎖定異常可能會令人困惑,並且很難調試。有人可能從我的經歷中受益。

問候 Lyju

+0

你在'@Transactional'旁邊的'Required'是什麼意思? – tkr 2018-03-01 02:51:34

+0

它是傳播,我只是沒有在那裏輸入它,它實際上是@Transactional(propagation = Propagation.REQUIRED)請檢查https://docs.spring.io/spring/docs/4.2.x/spring-框架引用/ html/transaction.html – 2018-03-01 09:32:26

1

我的經歷在我的項目的不同背景下的同樣的問題,也有像

- object is accessed from various source like (server side and client) 
- without any interval accessing the same object from a different place 

不同的場景在第一種情況下

當我發出了一個服務器卡爾,之前保存該對象他們從js的一個電話,並試圖保存和另一個地方,我得到了,js電話是去二,三次(我的事情叫綁定的事情導致問題)

我解決了

e.preventDefault() 

第二種情況,

object.lock() 
0

出現此錯誤對我來說,當我試圖從兩個不同的會話更新同一行。我在一個瀏覽器中更新了一個字段,而第二個瀏覽器已打開並且已將原始對象存儲在其會話中。當我試圖從第二個「過時」會話更新時,我得到了陳舊的對象錯誤。爲了糾正這個問題,我在設置要更新的值之前重新獲取要從數據庫更新的對象,然後將其保存爲正常。

0

萬一有人檢查此線程,並有同樣的問題,因爲我的...

行被其它事務更新或刪除(或者未保存值的映射是不正確的)

我正在使用NHibernate,我收到同樣的錯誤,在創建一個對象時...

我手動傳遞密鑰, e, 所以一旦我刪除了GUID,並將該字段留空,一切都很順利。

這個答案可能不會幫助你,但會幫助我這樣的人,誰只是你的線程becasue同樣的錯誤的

2

我對我的項目中這個問題。

執行樂觀鎖定後,我得到了同樣的異常。 我的錯誤是我沒有刪除成爲@Version的字段的二傳手。由於在java空間中調用setter,字段的值與DB生成的值不再匹配。所以基本上版本字段不再匹配。在這一點上的實體進行任何修改導致:

org.hibernate.StaleObjectStateException:行被更新或者通過 另一個事務刪除(或者未保存值的映射是不正確的)

我使用內存數據庫中的H2和休眠。

-3

我在我的一個應用程序中遇到了這個問題,現在,我知道這是一箇舊線程,但這裏是我的解決方案;通過查看調試器中的數據,我發現JVM實際上在Hibernate嘗試更新數據庫(實際上是在另一個線程中完成的)中未正確加載它,因此我將關鍵字「volatile」添加到每個字段的實體。它有一些性能問題,要做到這一點,而是,除重物beeing拋來拋去......

-1

爲了防止StaleObjectStateException,在hbm文件寫入下面的代碼:

<timestamp name="lstUpdTstamp" column="LST_UPD_TSTAMP" source="db"/> 
+0

我相信您的文章中缺少實際的答案... – Ajean 2015-10-30 15:31:26

+0

@Yusuf - 提示:要確保XML代碼可見,請使用工具欄中的代碼按鈕「{}」或縮進四(4)個空格。 – Leigh 2015-10-31 01:21:50

+0

你應該解釋你的代碼在做什麼以及它如何修復這個問題。儘管如此,我不認爲這是一個解決方案。 – 2017-01-19 14:45:02

1

我有同樣的問題在我的情況下,該問題在實體對象的某些類型的字段中丟失和/或不正確等於實現。在提交時,Hibernate檢查會話中加載的所有實體,以檢查它們是否髒。如果任何實體都是髒的,hibernate會嘗試持久化它們 - 不管事實上請求保存操作的實際對象與其他實體無關。

如果財產有關聯org.Hibernate.UserType,實體骯髒是通過比較給定對象的每個屬性(與他們的等價方法)或UserType.equals完成。

另一件讓我感到意外的事情是,在我的交易中(使用Spring註釋@Transactional),我正在處理一個實體。 Hibernate抱怨一些隨機的實體與那個被保存的實體無關。我意識到存在一個我們在REST控制器級別創建的最外層事務,所以會話的範圍太大,因此作爲請求處理一部分加載的所有對象都會被檢查爲髒。

希望這有助於某人,某天。

Thanks Rags

0

我在我的grails項目中遇到了同樣的問題。問題是,我覆蓋了收集字段的getter方法。這在其他線程中總是返回集合的新版本。

class Entity { 
    List collection 

    List getCollection() { 
     return collection.unique() 
    } 
} 

的解決方案是重命名getter方法:

class Entity { 
    List collection 

    List getUniqueCollection() { 
     return collection.unique() 
    } 
} 
相關問題