JUnitやバッチテストで便利！

# ✅ JUnitやバッチテストで便利！INSERTだけでテストデータの削除＆再登録を自動化

テストコードを書くとき、毎回SQLで `DELETE → INSERT` を準備するのって面倒ですよね？  
本記事では、**「INSERT文だけ書けば、既存データを削除して再登録してくれる」**便利なJavaユーティリティクラス `TestDataLoader` を紹介します。

---

## 💡 こんな人におすすめ

- バッチ処理の単体／結合テストを行いたい
- テスト前にデータをきれいに準備しておきたい
- 何度もテストを繰り返してもDBが汚れないようにしたい

---

## 🎯 解決したい課題

例えばこんなことをやりたいとします：

```sql
-- テスト用データの準備
INSERT INTO users (id, name, email) VALUES (1001, 'Alice', 'alice@example.com');
```

ですが、すでに `id = 1001` のデータが存在すると `重複エラー` が出てしまいます。

そこで...

```sql
-- 毎回書くのは面倒…
DELETE FROM users WHERE id = 1001;
INSERT INTO users (id, name, email) VALUES (1001, 'Alice', 'alice@example.com');
```

このような処理を**Java側で自動化**してくれるのが `TestDataLoader` です！

---

## ✅ 使い方

### 1. SQLファイルを用意（例：`test_data.sql`）

```sql
-- INSERT文だけでOK（DELETEは不要！）
INSERT INTO users (id, name, email) VALUES (1001, 'Alice', 'alice@example.com');
INSERT INTO users (id, name, email) VALUES ('U0002', 'Bob', 'bob@example.com');
```

### 2. Javaコードで呼び出し

```java
Connection conn = DriverManager.getConnection(...);
conn.setAutoCommit(false); // テストなら rollback できるように

TestDataLoader.loadTestData(conn);

conn.commit(); // または conn.rollback();
```

---

## 🧠 実装のポイント（中身）

- `INSERT` 文を読み取って、`id` カラムを探す
- `id` の値で `SELECT COUNT(*)` を実行して存在確認
- データがあれば `DELETE` → `INSERT`
- なければそのまま `INSERT`

```java
String checkSql = "SELECT COUNT(*) FROM users WHERE id = 1001";
```

---

## 📦 TestDataLoader.java（コード全文）

```java
package jp.test.util;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.sql.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TestDataLoader {

    /**
     * test_data.sql に記載された SQL を読み込み、データ削除および挿入を行う。
     * 基本的には INSERT 文を対象とし、ID 重複があれば DELETE → INSERT。
     */
    public static void loadTestData(Connection conn) throws Exception {
        try (
            // ステートメント（SQL実行用）を作成
            Statement stmt = conn.createStatement();

            // test_data.sql を UTF-8 で読み込み、1行ずつ処理する
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(
                    TestDataLoader.class.getClassLoader().getResourceAsStream("test_data.sql"),
                    "UTF-8"
                ))
        ) {
            String line;
            // SQLファイルを1行ずつ読み込む
            while ((line = reader.readLine()) != null) {
                line = line.trim();  // 空白削除

                // 空行またはコメント行（--で始まる）はスキップ
                if (line.isEmpty() || line.startsWith("--")) continue;

                // INSERT文の場合
                if (line.toUpperCase().startsWith("INSERT")) {
                    // テーブル名、カラム名、値を抽出
                    String table = extractTableName(line);
                    String[] columns = extractColumns(line);
                    String[] values = extractValues(line);

                    // パースに失敗したらスキップ
                    if (table == null || columns == null || values == null) {
                        System.err.println("解析失敗: " + line);
                        continue;
                    }

                    // "id" カラムの位置を確認
                    Integer idIndex = findIndex(columns, "id");
                    if (idIndex != null) {
                        // idカラムの値を取得
                        String idValue = values[idIndex].trim();

                        // 文字列型ならクォートで囲む
                        if (!idValue.startsWith("'") && !isNumeric(idValue)) {
                            idValue = "'" + idValue + "'";
                        }

                        // 該当IDのレコードが存在するか確認
                        String selectSql = "SELECT COUNT(*) FROM " + table + " WHERE id = " + idValue;
                        ResultSet rs = stmt.executeQuery(selectSql);
                        rs.next();
                        int count = rs.getInt(1);
                        rs.close();

                        // 存在していれば DELETE 実行
                        if (count > 0) {
                            String deleteSql = "DELETE FROM " + table + " WHERE id = " + idValue;
                            stmt.executeUpdate(deleteSql);
                            System.out.println("削除実行: " + deleteSql);
                        } else {
                            System.out.println("データ未存在 → INSERT 実行");
                        }
                    } else {
                        // id カラムが見つからなければ削除はスキップ
                        System.out.println("'id' カラムが見つからない → 削除スキップ");
                    }

                    // 最終的に INSERT を実行
                    stmt.executeUpdate(line);
                    System.out.println("挿入成功: " + line);

                } else {
                    // その他の SQL（DELETEやUPDATEなど）も実行可
                    stmt.executeUpdate(line);
                    System.out.println("その他SQL実行: " + line);
                }
            }
        }
    }

    /**
     * INSERT文からテーブル名を抽出
     */
    private static String extractTableName(String sql) {
        Pattern p = Pattern.compile("INSERT\\s+INTO\\s+(\\w+)", Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(sql);
        return m.find() ? m.group(1) : null;
    }

    /**
     * INSERT文からカラム名の配列を抽出
     */
    private static String[] extractColumns(String sql) {
        int start = sql.indexOf('(');
        int end = sql.indexOf(')', start);
        if (start < 0 || end < 0) return null;
        return sql.substring(start + 1, end).split(",");
    }

    /**
     * INSERT文から VALUES 内の値の配列を抽出
     */
    private static String[] extractValues(String sql) {
        int start = sql.toUpperCase().indexOf("VALUES");
        int open = sql.indexOf('(', start);
        int close = sql.indexOf(')', open);
        if (start < 0 || open < 0 || close < 0) return null;
        return sql.substring(open + 1, close).split(",");
    }

    /**
     * 指定されたカラム名（key）が配列の何番目にあるかを返す
     */
    private static Integer findIndex(String[] columns, String key) {
        for (int i = 0; i < columns.length; i++) {
            if (columns[i].trim().equalsIgnoreCase(key)) {
                return i;
            }
        }
        return null;
    }

    /**
     * 数値かどうかを判定（例: '123' → OK）
     */
    private static boolean isNumeric(String value) {
        try {
            Double.parseDouble(value.replace("'", ""));
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }
}

```

---

## 🔧 カスタマイズ案（応用したくなったら）

- `id` 以外のキーで存在チェックしたい（例：`email` など）
- 複合キーに対応したい
- `MERGE INTO` を使った UPSERT にしたい（DB2限定）

---

## 📌 まとめ

| メリット | 内容 |
|----------|------|
| ✅ SQLファイルの準備が楽になる | INSERTだけでOK |
| ✅ JUnitやバッチテスト前の初期化に使える | @Beforeにも組み込み可 |
| ✅ 何度テストしてもDBが汚れない | 自動でDELETEしてくれる |

---

## 🛠 実行例ログ（JUnitやmainで実行した場合）

```text
✅ DB接続成功  
🗑️ 削除実行: DELETE FROM users WHERE id = 1001  
✅ 挿入成功: INSERT INTO users (id, name, email) VALUES (1001, 'Alice', 'alice@example.com')  
```

---

## 💬 コメント歓迎！

改善アイデアや、「こういうキーでも消したい」といった要望があれば、お気軽にどうぞ！