Java | 「for-each+例外処理+ログ」のテンプレートをクラス構造で整理(実務クラス設計例)

Java Java
スポンサーリンク

ここでは、Spring Boot + SLF4J(Logback) を前提にした、
「for-each+例外処理+ロギング」を安全かつ拡張性高く実装する 実務テンプレート を紹介します。

これは実際の業務システム(バッチ処理・ETL・データ連携など)で
“安全に全件処理を継続する”ためのベース設計としてそのまま使える構成です。


Spring Boot+SLF4J対応 for-each 安全処理テンプレート

package com.example.processor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 拡張for文を安全に処理する共通テンプレート
 */
@Slf4j
@Service
public class SafeForEachProcessor<T> {

    /**
     * for-each処理を安全に実行(例外とログを統一管理)
     */
    public void process(List<T> items, SafeHandler<T> handler) {
        if (items == null || items.isEmpty()) {
            log.warn("処理対象リストが空またはnullです。スキップします。");
            return;
        }

        int successCount = 0;
        int failureCount = 0;

        for (T item : items) {
            try {
                handler.handle(item);
                successCount++;
            } catch (BusinessException e) {
                failureCount++;
                log.warn("業務エラー(スキップ): {} -> {}", item, e.getMessage());
            } catch (Exception e) {
                failureCount++;
                log.error("予期せぬ例外発生: {}", item, e);
            }
        }

        log.info("処理完了: 成功={}件 / 失敗={}件 / 合計={}件",
                successCount, failureCount, items.size());
    }
}
Java

関連クラス構成(業務利用例)

SafeHandler.java(ラムダで処理定義)

package com.example.processor;

@FunctionalInterface
public interface SafeHandler<T> {
    void handle(T item) throws Exception;
}
Java

BusinessException.java(想定内エラー)

package com.example.processor;

public class BusinessException extends Exception {
    public BusinessException(String message) {
        super(message);
    }
}
Java

Customer.java(ドメインモデル例)

package com.example.model;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Customer {
    private String name;
    private int age;
}
Java

CustomerService.java(業務ロジック例)

package com.example.service;

import com.example.model.Customer;
import com.example.processor.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class CustomerService {

    public void processCustomer(Customer customer) throws BusinessException {
        if (customer.getAge() < 0) {
            throw new BusinessException("年齢が不正: " + customer);
        }
        // 実際の処理(DB更新・外部API呼び出しなど)
        log.info("顧客処理中: {}", customer.getName());
    }
}
Java

BatchJob.java(バッチ的な呼び出し例)

package com.example.job;

import com.example.model.Customer;
import com.example.processor.SafeForEachProcessor;
import com.example.service.CustomerService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Component
@RequiredArgsConstructor
public class BatchJob {

    private final SafeForEachProcessor<Customer> processor;
    private final CustomerService customerService;

    public void run() {
        List<Customer> customers = Arrays.asList(
                new Customer("Alice", 25),
                new Customer("Bob", -1),
                new Customer("Charlie", 30)
        );

        processor.process(customers, customerService::processCustomer);
    }
}
Java

DemoApplication.java(起動クラス)

package com.example;

import com.example.job.BatchJob;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

    private final BatchJob batchJob;

    public DemoApplication(BatchJob batchJob) {
        this.batchJob = batchJob;
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void run(String... args) {
        batchJob.run();
    }
}
Java

実行結果ログ(例)

INFO  com.example.processor.SafeForEachProcessor - 処理対象リスト: 3件
INFO  com.example.service.CustomerService - 顧客処理中: Alice
WARN  com.example.processor.SafeForEachProcessor - 業務エラー(スキップ): Customer(name=Bob, age=-1) -> 年齢が不正: Customer(name=Bob, age=-1)
INFO  com.example.service.CustomerService - 顧客処理中: Charlie
INFO  com.example.processor.SafeForEachProcessor - 処理完了: 成功=2件 / 失敗=1件 / 合計=3件

実務ベストプラクティスまとめ

項目ベストプラクティス理由
ログ出力にSLF4J+LogbackSystem.out禁止。統一されたフォーマット+レベル管理可能。
for-each内で個別try-catch1件失敗しても全体継続できる。バッチ処理の基本。
共通テンプレート化全サービスで同じ安全基盤を使い回せる。例外処理重複を削減。
ビジネス例外クラスを分離想定内エラーと致命的障害を区別可能。監視・通知設定がしやすい。
DI構成(Spring管理)テスト容易性・拡張性が高い(Mock化・単体テスト可能)。
結果サマリー出力運用監視で「件数把握」が非常に重要。

発展案(次ステップ)

機能説明
🔁 リトライ付き処理(一定回数まで再実行)API連携や一時障害に強い
🧮 処理結果オブジェクト(成功件数・失敗リスト)返却集計レポート生成に利用
🪶 非同期処理(@Async)対応版並列実行でバッチ性能向上
🧱 テストテンプレート(JUnit+MockBean)安全動作を検証しやすくする
タイトルとURLをコピーしました