82

Web サーバー上で実行される Java プロジェクトがあります。私はいつもこの例外に遭遇します。

いくつかのドキュメントを読んだところ、悲観的ロック (または楽観的ですが、悲観的の方が優れていると読みました) がこの例外を防ぐ最善の方法であることがわかりました。

しかし、その使用方法を説明する明確な例は見つかりませんでした。

私の方法は次のようなものです:

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

その間:

  • EmailHibernate クラスです (データベース内のテーブルになります)
  • getEmailById(String id)を返す関数ですemail(このメソッドには の注釈が付けられていません@Transactional) 。
  • updateEmail(email): メールを更新するメソッドです。

注:保存、更新などにHibernateを使用しています(例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 
4

21 に答える 21

66

悲観的ロックは一般的に推奨されておらず、データベース側のパフォーマンスの点で非常にコストがかかります。あなたが言及した問題(コード部分)は、次のようないくつかの点が明確ではありません。

  • コードが複数のスレッドによって同時にアクセスされている場合。
  • どのようにsessionオブジェクトを作成していますか (Spring を使用しているかどうかわかりません)?

Hibernate Session オブジェクトはスレッドセーフではありません。そのため、複数のスレッドが同じセッションにアクセスし、同じデータベース エンティティを更新しようとすると、コードがこのようなエラー状態になる可能性があります。

したがって、ここで何が起こるかというと、複数のスレッドが同じエンティティを更新しようとし、1 つのスレッドが成功し、次のスレッドがデータをコミットしようとすると、既に変更されていることがわかり、最終的にStaleObjectStateException.

編集

Hibernate で悲観的ロックを使用する方法があります。このリンクをチェックしてください。しかし、このメカニズムには問題があるようです。ただし、休止状態 ( HHH-5275 ) にバグを投稿することに遭遇しました。バグで言及されているシナリオは次のとおりです。

2 つのスレッドが同じデータベース レコードを読み取っています。これらのスレッドの 1 つは悲観的ロックを使用し、それによって他のスレッドをブロックする必要があります。ただし、両方のスレッドがデータベース レコードを読み取ることができるため、テストは失敗します。

これはあなたが直面しているものに非常に近いです。これが機能しない場合は、これを試してください。私が考えることができる唯一の方法は、クエリを使用して postgresデータベースで悲観的ロックを実現できるネイティブ SQL クエリを使用することです。SELECT FOR UPDATE

于 2011-12-27T14:58:53.503 に答える
20

データをポーリングし、処理のためにハンドラーに渡すキュー マネージャーがあります。同じイベントが再び取得されるのを避けるために、キュー マネージャーはデータベース内のレコードを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()

問題を修正し、現在は正常に表示されています。楽観的ロックの例外は混乱を招く可能性があり、デバッグが難しいため、共有することを考えました。

于 2013-09-27T01:41:22.800 に答える
16

データベースから取得した電子メールを実際に使用しているようには見えませんが、パラメータとして取得した古いコピーを使用しています。行のバージョン管理に使用されているものはすべて、以前のバージョンが取得されたときと更新を行っているときとの間に変更されています。

おそらく、コードを次のようにしたいと思うでしょう:

    @Transactional
    public void test(String id, String subject) {
       Email email = getEmailById(id);
       email.setSubject(subject);
       updateEmail(email);
    }
于 2011-12-27T14:29:17.367 に答える
6

この例外は、楽観的ロック (またはコードのバグ) が原因である可能性があります。知らず知らずのうちに使っていることでしょう。そして、疑似コード (問題を診断できるように実際のコードに置き換える必要があります) が間違っています。Hibernate は、アタッチされたエンティティに対して行われたすべての変更を自動的に保存します。添付されたエンティティに対して update、merge、または saveOrUpdate を呼び出すべきではありません。やるだけ

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

update を呼び出す必要はありません。Hibernate は、トランザクションをコミットする前に変更を自動的にフラッシュします。

于 2011-12-27T14:34:54.707 に答える
2

オブジェクトが 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;
    }
于 2012-10-23T11:27:30.570 に答える
2

プロジェクトのさまざまなコンテキストで同じ問題を経験しましたが、次のようなさまざまなシナリオがあります

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

最初のケースでは

サーバーcalを発行すると、そのオブジェクトを保存する前に、jsからの1回の呼び出しを保存しようとして、別の場所で、js呼び出しが2、3回行われているようになりました(バインディングの呼び出しが問題を引き起こしていると思います)

で解決しました

e.preventDefault()

2番目のケース、

object.lock()
于 2014-05-23T06:16:18.190 に答える
-1

を防ぐためStaleObjectStateExceptionに、hbmファイルに以下のコードを記述します。

<timestamp name="lstUpdTstamp" column="LST_UPD_TSTAMP" source="db"/>
于 2015-10-30T11:27:41.587 に答える
-1

最初にインポートを確認します。セッションを使用する場合、トランザクションは org.hibernate であり、@Transactinal注釈を削除する必要があります。エンティティクラスで最も重要なのは@GeneratedValue(strategy=GenerationType.AUTO)、モデルオブジェクトの作成時またはエンティティオブジェクトの作成時に id を作成しないでください。最終的な結論は、パスIDを提出したい場合、つまりPK@GeneratedValueをエンティティクラスから削除することです。

于 2018-10-24T14:34:45.563 に答える
-3

アプリの 1 つでこの問題が発生しましたが、これは古いスレッドであることがわかりましたが、これが私の解決策です。デバッガー内のデータを見て、Hibernate がデータベースを更新しようとしているときに (実際には別のスレッドで行われている)、JVM が実際に適切にロードしなかったことがわかったので、すべてのフィールドにキーワード「volatile」を追加しました。エンティティの。それを行うにはいくつかのパフォーマンスの問題がありますが、重いオブジェクトが飛び交うよりも...

于 2015-04-22T20:23:29.753 に答える