Java | 実務対応版:「安全なリトライ処理テンプレート集(ログ+例外対応)」

Java Java
スポンサーリンク

これは「break を使ったループ制御+安全設計」を実務テンプレート化したものです。
Java+SLF4J/Spring Boot想定で、そのまま業務コードに組み込みやすい形にしてあります。
すべて 例外・ログ・上限制御・後処理 を備えています。


1. 基本構造(安全リトライの考え方)

┌──────────────┐
│ try 処理                │
│  ├ 成功 → break        │
│  └ 失敗 → ログ          │
│     + 待機              │
│(上限まで繰返)           │
└──────────────┘
        ↓
    終了処理・通知

特徴

✅ 無限ループしない(上限付き)
✅ ログに「何回目の失敗か」を残す
✅ 例外を握りつぶさず、最後に再スロー可能
✅ Thread.sleep で簡易バックオフ対応


テンプレート①:基本リトライ(IOException想定)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;

public class RetryExample {
    private static final Logger log = LoggerFactory.getLogger(RetryExample.class);
    private static final int MAX_RETRY = 3;

    public static void main(String[] args) {
        boolean success = false;

        for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
            try {
                log.info("API呼び出し試行 {} 回目", attempt);
                callApi(); // 仮の外部処理

                log.info("処理成功");
                success = true;
                break; // 成功したら終了

            } catch (IOException e) {
                log.warn("試行 {} 回目で失敗: {}", attempt, e.getMessage());
                if (attempt == MAX_RETRY) {
                    log.error("最大試行回数に達しました。処理を中断します。", e);
                } else {
                    try {
                        Thread.sleep(1000L * attempt); // バックオフ(1s,2s,3s)
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        log.error("スリープ中断", ie);
                        break;
                    }
                }
            }
        }

        if (!success) {
            log.error("すべての試行に失敗しました。");
        }
    }

    private static void callApi() throws IOException {
        // 実際には外部API呼び出しなど
        if (Math.random() < 0.7) { // 70%の確率で失敗
            throw new IOException("一時的な通信エラー");
        }
    }
}
Java

🧭 ポイント解説

  • break:成功時にループを抜ける
  • MAX_RETRY:無限リトライ防止
  • Thread.sleep():簡易的なバックオフ制御
  • ログレベル:
    • info:開始・成功
    • warn:再試行中
    • error:全失敗

テンプレート②:メソッド分割+再スロー(実務クラス構成)

public class ApiService {
    private static final Logger log = LoggerFactory.getLogger(ApiService.class);
    private static final int MAX_RETRY = 5;

    public void executeWithRetry() throws IOException {
        for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
            try {
                callExternalApi();
                log.info("外部API呼び出し成功");
                break;

            } catch (IOException e) {
                log.warn("試行 {} 回目で失敗: {}", attempt, e.getMessage());
                if (attempt == MAX_RETRY) {
                    log.error("最大リトライ回数に達しました。例外を再スローします。", e);
                    throw e; // 呼び出し元でハンドリング
                }
                waitBeforeRetry(attempt);
            }
        }
    }

    private void callExternalApi() throws IOException {
        if (Math.random() < 0.5) throw new IOException("通信エラー");
    }

    private void waitBeforeRetry(int attempt) {
        try {
            Thread.sleep(1000L * attempt);
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            log.error("スリープ中断", ie);
        }
    }
}
Java

💡 現場でのポイント

  • 実務ではメソッドを分割して「責務」を明確にする。
  • throw e; で上位層(Service / Controller)に例外を伝播。
  • テスト時は Math.random() をモックに置き換える。

テンプレート③:Spring Boot + @Retryable(ライブラリ利用)

Spring Bootであれば、spring-retry を利用することで安全なリトライを宣言的に書けます。
内部的には break と同様の制御を自動で行います。

import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.annotation.Backoff;
import org.springframework.stereotype.Service;
import java.io.IOException;

@Service
public class RetryableService {

    @Retryable(
        value = IOException.class,
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000, multiplier = 2)
    )
    public void call() throws IOException {
        if (Math.random() < 0.8) {
            throw new IOException("APIエラー");
        }
        System.out.println("成功しました");
    }
}
Java

🧩 特徴

  • maxAttempts:リトライ上限
  • @Backoff(delay, multiplier):指数バックオフ
  • 明示的なループ不要(内部で break 相当の制御)
  • 実務では「Spring + SLF4J」構成が多い

テンプレート④:Result型+ループ終了判定(関数型風)

副作用を減らしたい場合、結果を Result オブジェクトで包んで戻す方法もあります。

public class SafeRetry {

    public record Result(boolean success, String message) {}

    public Result doRetryableJob() {
        for (int attempt = 1; attempt <= 3; attempt++) {
            try {
                perform();
                return new Result(true, "成功 " + attempt + "回目");
            } catch (Exception e) {
                if (attempt == 3) {
                    return new Result(false, "失敗: " + e.getMessage());
                }
            }
        }
        return new Result(false, "不明な終了");
    }

    private void perform() throws Exception {
        if (Math.random() < 0.8) throw new Exception("一時エラー");
    }
}
Java

テンプレート⑤:リトライ付きDB接続例(擬似)

Connection conn = null;
for (int attempt = 1; attempt <= 3; attempt++) {
    try {
        conn = DriverManager.getConnection(url, user, pass);
        System.out.println("DB接続成功");
        break;
    } catch (SQLException e) {
        System.err.println("接続失敗 " + attempt + "回目: " + e.getMessage());
        if (attempt == 3) throw e;
        Thread.sleep(500 * attempt);
    }
}
Java

実務の安全設計ポイントまとめ

項目ベストプラクティス
🔁 リトライ回数固定上限を必ず設ける(無限ループ禁止)
⏱ バックオフThread.sleep()@Backoff で段階的遅延
🪵 ロギング成功・失敗・最終失敗をすべて残す
🧨 例外必要に応じて上位層に再スロー
🧹 後処理finally または try-with-resources でリソース解放
🔒 並列実行スレッドセーフなAPIで書く(synchronizedやExecutor管理)

✅ まとめ

break文+上限+例外+ログを組み合わせれば、
実務でも安全に「失敗に強い」ループ処理が書ける。

タイトルとURLをコピーしました