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
悲観的ロックは一般に推奨されておらず、データベース側のパフォーマンスの点で非常にコストがかかります。あなたが言及した問題(コード部分)のようないくつかのことは明確ではありません:
session
オブジェクトをどのように作成していますか(Springを使用しているかどうかわかりません)。Hibernate セッションオブジェクトはスレッドセーフではありません 。したがって、同じセッションにアクセスし、同じデータベースエンティティを更新しようとする複数のスレッドがある場合、コードはこのようなエラー状態になる可能性があります。
したがって、ここで何が起こるかというと、複数のスレッドが同じエンティティを更新しようとし、1つのスレッドが成功し、次のスレッドがデータをコミットすると、すでに変更されていることがわかり、StaleObjectStateException
がスローされます。
編集:
Hibernateで悲観的ロックを使用する方法があります。 このリンク をご覧ください。しかし、このメカニズムには何らかの問題があるようです。ただし、休止状態のバグ( HHH-5275 )を投稿することに遭遇しました。バグに記載されているシナリオは次のとおりです。
2つのスレッドが同じデータベースレコードを読み取ります。これらのスレッドの1つは悲観的ロックを使用して、他のスレッドをブロックする必要があります。ただし、両方のスレッドがデータベースレコードを読み取ることができるため、テストが失敗します。
これはあなたが直面しているものに非常に近いです。これが機能しない場合はこれを試してください、私が考えることができる唯一の方法は、 ネイティブSQLクエリ を使用できる場合です postgresの悲観的ロックSELECT FOR UPDATE
を使用したデータベースクエリ。
データベースから取得した電子メールを実際に使用しているようには見えませんが、パラメータとして取得した古いコピーを使用しています。行のバージョン管理に使用されているものはすべて、以前のバージョンが取得されてから更新を実行するまでの間で変更されています。
おそらく、コードを次のようにする必要があります。
@Transactional
Public void test(String id, String subject){
Email email = getEmailById(id);
email.setSubject(subject);
updateEmail(email);
}
私はこれが古い質問であることを知っていますが、私たちの何人かはまだそれを打っていて、空をさまよう方法を見ています。これが私が直面した問題の一種です。
データをポーリングし、処理のためにハンドラーに渡すキューマネージャーがあります。同じイベントが再び取得されるのを避けるために、キューマネージャーはLOCKED状態でデータベース内のレコードをロックします。
void poll() {
record = dao.getLockedEntity();
queue(record);
}
このメソッドはトランザクションではありませんでしたが、dao.getLockedEntity()
は 'REQUIRED'のトランザクションです。
すべて順調で、実稼働中の数か月後、楽観的なロック例外で失敗しましたが、
多くのデバッグと詳細の確認の後、誰かがこのようにコードを変更したことがわかりました。
@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
void poll() {
record = dao.getLockedEntity();
queue(record);
}
したがって、レコードはdao.getLockedEntity()のトランザクションがコミットされる前にキューに入れられ(pollメソッドの同じトランザクションを使用)、poll()メソッドのトランザクションが取得されるまでにオブジェクトがハンドラー(異なるスレッド)によって変更されましたコミット。
この問題を修正しましたが、今では問題ありません。
楽観的なロック例外は混乱を招く可能性があり、デバッグが難しいため、共有することを考えました。誰かが私の経験から恩恵を受けるかもしれません。
よろしくリュジュ
私のプロジェクトでこの問題が発生しました。
楽観的ロックを実装した後、同じ例外が発生しました。私の間違いは、@Version
になったフィールドのセッターを削除しなかったことです。セッターはJavaスペースで呼び出されていたため、フィールドの値はDBによって生成された値と一致しなくなりました。そのため、基本的にバージョンフィールドは一致しませんでした。その時点で、エンティティの変更は次のようになりました。
org.hibernate.StaleObjectStateException:別のトランザクションによって行が更新または削除されました(または未保存値のマッピングが正しくありませんでした)
私はメモリDBおよびHibernateでH2を使用しています。
この例外は、おそらく楽観的ロック(またはコードのバグ)が原因です。おそらく知らないうちに使用しているでしょう。そして、擬似コード(問題を診断できるように実際のコードに置き換える必要があります)が間違っています。 Hibernateは、接続されたエンティティに対して行われたすべての変更を自動的に保存します。接続されたエンティティでupdate、merge、saveOrUpdateを呼び出すべきではありません。ただやる
Email email = session.get(emailId);
email.setSubject(subject);
更新を呼び出す必要はありません。 Hibernateは、トランザクションをコミットする前に変更を自動的にフラッシュします。
オブジェクトがDBに存在するかどうかを確認し、存在する場合はオブジェクトを取得して更新します。
if (getEntityManager().contains(instance)) {
getEntityManager().refresh(instance);
return instance;
}
上記のif条件に失敗した場合、DBでIdを持つオブジェクトを見つけ、必要な操作を行います。この場合、変更は正確に反映されます。
if (....) {
} else if (null != identity) {
E dbInstance = (E) getEntityManager().find(instance.getClass(), identity);
return dbInstance;
}
Id
は自動生成されるため、保存するオブジェクトにId
を設定しないでください。
私はプロジェクトの異なるコンテキストで同じ問題を経験しましたが、次のような異なるシナリオがあります
- object is accessed from various source like (server side and client)
- without any interval accessing the same object from a different place
最初の場合
サーバーcalを発行すると、そのオブジェクトをjsから呼び出して別の場所に保存しようとする前に、js呼び出しが2回、3回行われるようになりました(バインディングを呼び出すと問題が発生する)
私が解決した
e.preventDefault()
2番目のケース、
object.lock()
また、新しい行を作成した後に既存の行を更新しようとしたときにこのエラーに遭遇し、主キー列のいずれかのタイプが間違っています。
LocalDate
を使用すべきだったときにLocalDateTime
を使用しました。これにより、休止状態がエンティティを識別できなくなり、このエラーが発生したと思います。
キーをLocalDateTime
に変更すると、エラーはなくなりました。また、個々の行の更新も機能し始めました。以前は更新用の行が見つからなかったため、この個別の問題をテストすることが実際に主キーマッピングに関する結論に至りました。
誰かがこのスレッドをチェックし、私のものと同じ問題を抱えていた場合に備えて...
行が別のトランザクションによって更新または削除されました(または未保存値のマッピングが正しくありませんでした)
私はNHibernateを使用していますが、オブジェクトの作成中に同じエラーが発生します...
キーを手動で渡し、マッピングでGUIDジェネレーターも指定したため、hibernateはまったく同じエラーを生成するため、GUIDを削除してフィールドを空のままにすると、すべてがうまくいきました。
この答えはあなたには役に立たないかもしれませんが、同じエラーのためにあなたのスレッドだけである私のような人を助けます
私は同じ問題を抱えていましたが、私の場合は、エンティティオブジェクトの一部のタイプのフィールドで問題が見つからなかったり、間違った同等の実装が行われていました。コミット時に、Hibernateはセッションにロードされたすべてのエンティティをチェックして、それらがダーティかどうかをチェックします。エンティティのいずれかが汚れている場合、休止操作はそれらを永続化しようとします-保存操作を要求された実際のオブジェクトが他のエンティティに関連していないという事実に関係なく。
エンティティのダーティネスは、指定されたオブジェクトのすべてのプロパティを(等しいメソッドで)比較するか、プロパティにUserType.equals
が関連付けられている場合はorg.Hibernate.UserType
を比較することによって行われます。
もう1つ驚いたのは、トランザクション(Springアノテーション@Transactional
を使用)で、単一のエンティティを扱っていたことです。 Hibernateは、保存されているエンティティとは関係のないランダムなエンティティについて不平を言っていました。私が気づいたのは、RESTコントローラーレベルで作成する最も外側のトランザクションがあるため、セッションのスコープが大きすぎるため、要求処理の一部としてロードされたすべてのオブジェクトがダーティチェックされます。
これがいつか誰かを助けることを願っています。
ありがとうラグス
このエラーは、2つの異なるセッションから同じ行を更新しようとしたときに発生しました。あるブラウザーのフィールドを更新し、もう1つのブラウザーを開いて、そのセッションで既に元のオブジェクトを保存していました。この2番目の「古い」セッションから更新しようとすると、古いオブジェクトエラーが表示されます。これを修正するには、更新する値を設定する前にデータベースから更新するオブジェクトを再取得し、通常どおり保存します。
このような例外も受け取りましたが、問題はエンティティ識別子にありました。私はUUID
を使用していますが、Springがそれらを操作する方法にいくつかの問題があります。それで、この行をエンティティ識別子に追加したところ、動作が開始されました。
@Column(columnDefinition = "BINARY(16)")
ここ もう少し情報を見つけることができます。
私のgrailsプロジェクトでも同じ問題がありました。バグは、コレクションフィールドのgetterメソッドを上書きすることでした。これは、常に他のスレッドのコレクションの新しいバージョンを返しました。
class Entity {
List collection
List getCollection() {
return collection.unique()
}
}
解決策は、ゲッターメソッドの名前を変更することでした。
class Entity {
List collection
List getUniqueCollection() {
return collection.unique()
}
}