22

私はSpring2.5とHibernateJPAの実装をJavaと「コンテナー」管理トランザクションで使用しています。

バックグラウンドでデータを更新する「ユーザーコミット後」メソッドがあり、クライアントに表示されることはないため、例外にConcurrencyFailureException関係なくコミットする必要があります。StaleObjectStateException言い換えれば、楽観的なロックを悲観的にする必要があります。(メソッドの実行に少し時間がかかり、誰かが他のトランザクションでデータを変更した場合に発生する可能性があります)


べき等について多くのことを読みました。DEFAULT_MAX_RETRIESまたは6.2.7の検索で例外が発生した場合は、再試行してください。例または第14.5章。再試行します。私はここここのstackoverflowでも見つけました。

私はこれを試しました:

public aspect RetryOnConcurrencyExceptionAspect {

    private static final int DEFAULT_MAX_RETRIES = 20;
    private int maxRetries = DEFAULT_MAX_RETRIES;

    Object around(): execution( * * (..) ) && @annotation(RetryOnConcurrencyException) && @annotation(Transactional) {

        int numAttempts = 0;
          RuntimeException failureException = null;
          do {
                numAttempts++;
                try {
                    return proceed(); 
                } 
                catch( OptimisticLockingFailureException ex ) {
                    failureException = ex;
                }
                catch(ConcurrencyFailureException ex) {
                    failureException = ex;
                }
                catch( StaleObjectStateException ex) {
                    failureException = ex;
                }
          } while( numAttempts <= this.maxRetries );
          throw failureException;

    }
}

RetryOnConcurrencyException例外が発生した場合に再試行する必要があるメソッドをマークするためのアノテーションです。動作しませんでした...私もいくつかの方法を試しましたSELECT ... FOR UPDATEEntityManager.lock(...)

Springでこのような戦略を使用して、古いデータやダーティリードなどを回避するための最良の方法は何ですか?再試行?、同期?、JPAロック?、分離?、更新のために...を選択しますか?私はそれを機能させることができませんでした、そして私はどんな助けにも本当に満足しています。


これが私がやりたい擬似コードです:

void doSomething(itemId) {
    select something into A;
    select anotherthing into B;

    // XXX
    item = getItemFormDB( itemId ); // takes long for one user and for other concurrent user it could take less time
    item.setA(A);
    item.setB(B);

    // YYYY
    update item; 
}

//XXXと//YYYの間で、別のセッションがアイテムを変更する可能性があり、StaleObjectStateExceptionがスローされます。

4

4 に答える 4

9

私は解決策を得ましたが、それは醜いと思います。すべての RuntimeException をキャッチし、新しいトランザクションに対してのみ機能します。それをより良くする方法を知っていますか?問題はありますか?

まず、注釈を作成しました。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryingTransaction {
     int repeatCount() default 20;
}

次に、次のようなインターセプターを作成しました。

    public class RetryingTransactionInterceptor implements Ordered {
      private static final int DEFAULT_MAX_RETRIES = 20;
      private int maxRetries = DEFAULT_MAX_RETRIES;
      private int order = 1;

      @Resource
      private PlatformTransactionManager transactionManager;

      public void setMaxRetries(int maxRetries) {
          this.maxRetries = maxRetries;
      }
      public int getOrder() {
          return this.order;
      }
      public void setOrder(int order) {
          this.order = order;
      }

      public Object retryOperation(ProceedingJoinPoint pjp) throws Throwable {
          int numAttempts = 0;
          Exception failureException = null;
          do {
                numAttempts++;
                try {
                    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
                    TransactionStatus status = transactionManager.getTransaction(def);

                    Object obj = pjp.proceed();

                    transactionManager.commit(status);      

                    return obj;
                } 
                catch( RuntimeException re ) {
                    failureException = re;
                }
          } while( numAttempts <= this.maxRetries );
          throw failureException;
      }
}

春の applicationConfig.xml:

<tx:annotation-driven transaction-manager="transactionManager" order="10" />

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionSynchronizationName">
        <value>SYNCHRONIZATION_ALWAYS</value>
    </property>
</bean>

<bean id="retryingTransactionInterceptor" class="com.x.y.z.transaction.RetryingTransactionInterceptor">
    <property name="order" value="1" />
</bean>

<aop:config>
    <aop:aspect id="retryingTransactionAspect" ref="retryingTransactionInterceptor">
        <aop:pointcut 
            id="servicesWithRetryingTransactionAnnotation" 
            expression="execution( * com.x.y.z.service..*.*(..) ) and @annotation(com.x.y.z.annotation.RetryingTransaction)"/>
        <aop:around method="retryOperation" pointcut-ref="servicesWithRetryingTransactionAnnotation"/>
    </aop:aspect>
</aop:config>

そして、次のように注釈が付けられたメソッド:

@RetryingTransaction
public Entity doSomethingInBackground(params)...
于 2010-07-02T11:54:33.877 に答える
9

バージョン番号またはタイムスタンプのチェックが失敗した場合 (楽観的ロックが発生)、Spring Retryを使用してメソッド全体を再試行します。

構成

@Configuration
@EnableRetry
public class FooConfig {
     ...
}

使用法

@Retryable(StaleStateException.class)
@Transactional
public void doSomethingWithFoo(Long fooId){
    // read your entity again before changes!
    Foo foo = fooRepository.findOne(fooId);

    foo.setStatus(REJECTED)  // <- sample foo modification

} // commit on method end

プロジェクト構成

Spring Boot アプリケーションは有効な spring-retry バージョンを定義しているため、これだけが必要です。

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency> 
于 2017-08-04T20:11:11.210 に答える
2

これがあり、私たちがしていることは次のとおりです。

  1. セッションをフラッシュします (次の更新がキューに入れられる唯一のものになるようにするため)
  2. インスタンスをロードする
  3. 変更を行います
  4. StaleObjectStateException で、アクション キューをクリアする

    ((EventSource) session).getActionQueue().clear()
    

    #2からリトライ

最後に例外を再スローするための再試行カウンターがあります。

注:これは公式にサポートされている方法ではありません (Hibernate は、例外をスローしたセッションは破棄して再利用しないように明確に述べています)、既知の回避策です (セッションを選択的に削除できないという制限があります)。アクションを更新しますが、キュー全体をクリアする必要があります)。

于 2011-08-23T09:27:03.230 に答える
-2

ここで別のオプションを投げます: BoneCP ( http://jolbox.com ) は、失敗時にトランザクションを自動的に再試行することをサポートしています (DB がダウンしたとき、ネットワークが失敗したときなど)。

于 2010-07-07T08:42:46.660 に答える