17.

Spring + Ajax CSV アップロード|FormData / MultipartFile 実装

編集
この記事の要点
  • Java/Spring + Ajax で CSV ファイルをアップロードする実装パターン
  • クライアント: FormData に file input を入れて $.ajax で POST
  • processData: false + contentType: false の指定が必須(jQuery が勝手にエンコードしないように)
  • サーバ: コントローラで @RequestParam("file") MultipartFile file で受け取る
  • Spring 側で spring.servlet.multipart.max-file-size を設定(デフォルト 1MB)

概要

フロント側 JavaScript から Java (Spring Framework) で CSV ファイルを受け取る実装例です。Ajax + FormData を使うことで、画面遷移なしでアップロード進捗・完了を扱えます。

HTML

JavaScript (jQuery)

$("#upload").click(function() {
    var formData = new FormData($('#fileuploadform').get(0));

    $.ajax({
        method: 'POST',
        url: '/csvUpload',
        data: formData,
        processData: false,    // ★ FormData を文字列化させない
        contentType: false,    // ★ multipart/form-data の boundary を維持
        headers: { 'X-CSRF-TOKEN': csrfToken }  // CSRF 対策(Spring Security)
    }).done(function(res) {
        $('#result').text('成功: ' + res.message);
    }).fail(function(xhr) {
        $('#result').text('失敗: ' + (xhr.responseJSON?.error ?? xhr.status));
    });
});

JavaScript(fetch 版・モダン)

document.getElementById('upload').addEventListener('click', async () => {
    const file = document.getElementById('file').files[0];
    if (!file) { alert('ファイル未選択'); return; }

    const formData = new FormData();
    formData.append('file', file);

    try {
        const res = await fetch('/csvUpload', {
            method: 'POST',
            body: formData,
            headers: { 'X-CSRF-TOKEN': csrfToken }
            // ★ Content-Type は付けない(fetch が自動で multipart/form-data + boundary)
        });
        if (!res.ok) throw new Error('HTTP ' + res.status);
        const data = await res.json();
        document.getElementById('result').textContent = '成功: ' + data.message;
    } catch (e) {
        document.getElementById('result').textContent = '失敗: ' + e.message;
    }
});

Spring Controller

@RestController
public class CsvUploadController {

    @PostMapping("/csvUpload")
    public ResponseEntity> upload(
            @RequestParam("file") MultipartFile file) {

        if (file.isEmpty()) {
            return ResponseEntity.badRequest()
                .body(Map.of("error", "ファイルが空です"));
        }

        // 拡張子チェック
        String name = file.getOriginalFilename();
        if (name == null || !name.toLowerCase().endsWith(".csv")) {
            return ResponseEntity.badRequest()
                .body(Map.of("error", "CSV ファイルのみ対応"));
        }

        int rows = 0;
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {

            // BOM スキップ
            br.mark(1);
            int c = br.read();
            if (c != 0xFEFF) br.reset();

            String line;
            br.readLine(); // ヘッダ行スキップ
            while ((line = br.readLine()) != null) {
                String[] cols = line.split(",");
                // DB 登録など処理
                rows++;
            }
        } catch (IOException e) {
            return ResponseEntity.status(500)
                .body(Map.of("error", "読み込み失敗: " + e.getMessage()));
        }

        return ResponseEntity.ok(Map.of("message", rows + " 件を登録"));
    }
}

Spring 側のサイズ上限設定

デフォルトでは Spring Boot のアップロード上限は 1MB / 1MB(ファイル / リクエスト全体)です。大きなファイルを扱う場合は application.properties で拡張:

spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
# 一時ディレクトリ(メモリ閾値超え時の書き出し先)
spring.servlet.multipart.location=/tmp
# メモリに保持する閾値(これを超えるとディスクへ)
spring.servlet.multipart.file-size-threshold=2KB

processData / contentType を false にする理由

オプションデフォルトFormData 送信時に必要な値
processDatatrue(クエリ文字列化)false(FormData をそのまま送る)
contentTypeapplication/x-www-form-urlencodedfalse(ブラウザに任せて boundary 付き multipart に)

fetch API ではこの指定は不要 — body に FormData を入れれば自動的に正しいヘッダになります(むしろ手動で Content-Type を付けると boundary が消えて壊れます)。

よくあるエラー

  • 413 Payload Too Large: ファイルサイズが上限超え → spring.servlet.multipart.max-file-size を増やす。または Nginx の client_max_body_size
  • 403 Forbidden: CSRF トークン未送信(Spring Security)→ X-CSRF-TOKEN ヘッダ追加
  • 415 Unsupported Media Type: contentType: false を忘れている → jQuery が間違った Content-Type を送っている
  • 500 + FileNotFoundException tmp/...: Multipart の一時ディレクトリが書き込み不可 → spring.servlet.multipart.location を見直し
  • 日本語ファイル名が文字化け: file.getOriginalFilename() がブラウザ依存。URL エンコードする運用が安全

セキュリティ注意

  • 拡張子だけでなくマジックバイトもチェック(Polyglot ファイル対策)
  • アップロードしたファイルは Web ルート外に保存(直接 URL でアクセスされないように)
  • ファイル名は サーバ側で UUID 等にリネーム(パストラバーサル防止)
  • 1 リクエストあたりの行数上限を設ける(メモリ枯渇 / DoS 対策)
編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. インストール(eclipseプラグイン)
  2. クイックスタート
  3. プロジェクトの作成
  4. Spring Bootプロジェクトの作成
  5. Spring Bootプロジェクトの実行
  6. Spring BootでHello World!
  7. アノテーション一覧
  8. DB接続設定からエンティティおよびリポジトリの作成、値の取得まで(JPA編)
  9. DB接続設定や値の取得(JdbcTemplate編)
  10. ビューから値をモデルに格納しコントローラーで受け取る方法
  11. コントローラーにてモデルに値を格納してビューに渡す方法
  12. テンプレートエンジン
  13. ModelとModelAndViewの違い
  14. AOPの使用方法
  15. classpath: 内部ファイルの読み込み
  16. file: 外部ファイルの読み込み
  17. CSVファイルアップロード方法(Ajax)
  18. CSVファイルダウンロード方法(Ajax)
  19. Spring Bootプロジェクトのビルドと本番環境へのデプロイ方法(内部tomcat使用)
  20. Application.propertiesの環境依存設定の分割方法
  21. JPAにおけるEntityManagerの取得方法
  22. JPAにおけるjava.sql.Connectionの取得方法
  23. エラー一覧
  24. jarの引数を受け取る方法
  25. Spring BootでGmailからメール送信
  26. 複数のDBに接続する設定(Spring Boot & JPA編)
  27. ポート番号の変更
  28. Basic認証の実装と特定のURLに限定する方法
  29. Spring SecurityのBasic認証の無効化
  30. 独自のエラーページを定義する方法
  31. プロパティファイルの値やjar実行時の引数を取得する方法