/* * Copyright (c) 2016 NTT DATA Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jp.terasoluna.fw.batch.executor; import java.util.concurrent.TimeUnit; import jp.terasoluna.fw.batch.constants.LogId; import jp.terasoluna.fw.logger.TLogger; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DataAccessException; import org.springframework.transaction.TransactionException; /** * データベースアクセスで例外が発生した場合に、一定時間待ってからリトライするインターセプター実装。 * <p> * 例外発生後にデータベースに再度アクセスする際は、"リトライ実施前の待機時間"を経過した後に行う。<br> * リトライ回数は、"最大リトライ"回数までとなる。<br> * 前回のリトライから"リトライ回数リセットまでの経過時間"後、リトライ回数実施カウンタをリセットする。<br> * これらの設定はプロパティファイルの以下の項目で指定する。 * * <table border> * <tr> * <th>プロパティ名</th> * <th>説明</th> * <th>デフォルト値</th> * </tr> * <tr> * <td>batchTaskExecutor.dbAbnormalRetryMax</td> * <td>最大リトライ回数</td> * <td>0(回)</td> * </tr> * <tr> * <td>batchTaskExecutor.dbAbnormalRetryInterval</td> * <td>リトライ実施前の待機時間</td> * <td>20000(ミリ秒)</td> * </tr> * <tr> * <td>batchTaskExecutor.dbAbnormalRetryReset</td> * <td>リトライ回数リセットまでの経過時間</td> * <td>600000(ミリ秒)</td> * </tr> * </table> * </p> * <p> * また、本機能を利用するにはBean定義が必要となる。<br> * 以下はBean定義に記述される{@code JobStatusChanger}、{@code JobControlFinder}のインタフェースで定義されたメソッドに対して * {@code AdminConnectionRetryInterceptor}によるコネクションリトライを行うための設定例である。 * * <pre> * {@code * <bean id="adminConnectionRetryInterceptor" * class="jp.terasoluna.fw.batch.executor.AdminConnectionRetryInterceptor" /> * <aop:config> * <aop:pointcut id="adminConnectionRetryPointcut" * expression=" execution(* jp.terasoluna.fw.batch.executor.repository.JobStatusChanger.*(..)) * || execution(* jp.terasoluna.fw.batch.executor.repository.JobControlFinder.*(..))" /> * <aop:advisor advice-ref="adminConnectionRetryInterceptor" pointcut-ref="adminConnectionRetryPointcut" /> * </aop:config> * } * </pre> * </p> * <p> * 以下はリトライ対象となる例外である。 * <ol> * <li>org.springframework.dao.DataAccessException</li> * <li>org.springframework.transaction.TransactionException</li> * </ol> * リトライによってデータベースアクセスが成功した場合は、例外をスローすることなく処理を終了する(リトライを示すINFOログは出力される)。 * リトライ回数を超えたときは、最後に発生した例外をスローする。 * なお、"リトライ回数リセットまでの経過時間"を短めに設定すると(たとえば、"リトライ実施前の待機時間"以下のような設定をすると)、 * リトライ回数がすぐにリセットされてしまい例外がスローされる間スピンループしてしまう。このような設定は避けること。 * </p> * * @see org.springframework.dao.DataAccessException * @see org.springframework.transaction.TransactionException * @since 3.6 */ public class AdminConnectionRetryInterceptor implements MethodInterceptor { private static final TLogger LOGGER = TLogger .getLogger(AdminConnectionRetryInterceptor.class); /** * 最大リトライ回数 */ @Value("${batchTaskExecutor.dbAbnormalRetryMax:0}") private volatile long maxRetryCount; /** * データベース異常時のリトライ間隔(ミリ秒) */ @Value("${batchTaskExecutor.dbAbnormalRetryInterval:20000}") private volatile long retryInterval; /** * リトライ回数をリセットする、前回からの発生間隔(ミリ秒) */ @Value("${batchTaskExecutor.dbAbnormalRetryReset:600000}") private volatile long retryReset; /** * 対象となる例外が発生したときにデータベース接続のリトライを実施する。 * リトライは、"リトライ実施前の待機時間"の後に、最大リトライ回数を上限に行なう。 * 前回のリトライから"リトライ回数リセットまでの経過時間"が経過している場合は、リトライ実施回数カウンタをリセットする。 * * @param invocation 処理対象となるメソッド * @return メソッド実行結果 * @throws Throwable リトライ処理から外部にスローされるThrowable */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { long retryCount = 0L; Throwable cause = null; Object returnObject = null; long lastExceptionTime = System.currentTimeMillis(); while (true) { try { cause = null; returnObject = invocation.proceed(); break; } catch (DataAccessException | TransactionException e) { if (System.currentTimeMillis() - lastExceptionTime > retryReset) { retryCount = 0L; } lastExceptionTime = System.currentTimeMillis(); cause = e; if (retryCount >= maxRetryCount) { LOGGER.error(LogId.EAL025063, cause, maxRetryCount); break; } TimeUnit.MILLISECONDS.sleep(retryInterval); retryCount++; LOGGER.info(LogId.IAL025017, retryCount, maxRetryCount, retryReset, retryInterval); } } if (cause != null) { throw cause; } return returnObject; } }