Loading
  • LIGHT

  • DARK

ROUTE

ルートゼロの
アクティビティ

Spring BootでList更新する方法|for文・Stream・save/saveAllの違いを徹底解説

3

Spring BootでListを1件ずつ更新する方法|初心者でもわかるサンプルコード付き


はじめに

Spring Bootで開発していると、Listの中身を1件ずつ更新したい場面にしばしば直面します。例えば、データベースから取得したユーザー情報を加工して再保存するときや、外部APIのレスポンスを整形するときです。しかし実際にコードを書く段階になると、「for文でループするのが最適なのか」「Streamで書けばスマートなのか」「更新時にsavesaveAllをどう使い分けるべきか」と迷うことも多いのではないでしょうか。

本記事では、Spring BootにおけるList更新の基本から、代表的な実装方法(for-each/Stream/Iterator/save/saveAll)の比較、よくあるエラーの回避方法、さらに実務で役立つ考え方までを整理します。読了後には単なるコード例だけでなく、なぜその方法を選ぶのかまで理解でき、自信を持って実装に臨めるようになるはずです。


Spring BootでListを1件ずつ更新する基本

よくある利用シーン

  • DB更新:取得したエンティティの特定フィールドを加工して保存

  • APIレスポンス加工:外部サービスのデータを整形して返却

  • バッチ処理:大量データをルールに沿って逐次更新

「まとめて更新」より「条件付きで1件ずつ更新」が現場では多く、ループ処理の理解が欠かせません。

用語解説:List(リスト)
Javaのコレクション型のひとつ。複数のデータ(オブジェクト)を順番に格納でき、for文やStreamで繰り返し処理が可能です。

for文による基本実装例

for (User user : userList) {
    if ("ACTIVE".equals(user.getStatus())) {
        user.setPoint(user.getPoint() + 10);
    }
}

最もシンプルで直感的。小規模処理や初心者には最適です。

用語解説:for文
指定した回数だけ繰り返し処理を行うプログラム構文。Listの各要素に順番にアクセスできます。

for文とStreamの使い分けや書き換えについては『Javaのfor文をStreamに書き換えるには?Spring Bootでの実践例付きでわかる!』をご参照ください。

Streamを使った実装例

userList.stream()
        .filter(user -> "ACTIVE".equals(user.getStatus()))
        .forEach(user -> user.setPoint(user.getPoint() + 10));

宣言的でスッキリ書けますが、副作用を伴うためチーム規約によっては敬遠される場合もあります。

用語解説:Stream
Java8以降で導入されたコレクション操作の仕組み。filterやforEachなどのメソッドで、データの絞り込みや処理を簡潔に記述できます。


JPAのsave / saveAllを使った更新

Spring Data JPAでは、エンティティの保存にsavesaveAllが用意されています。

用語解説:JPA(Java Persistence API)
Javaでデータベース操作を簡単に行うための仕組み。エンティティ(データの設計図)を使って、DBの保存・取得・更新を行います。

用語解説:save / saveAll
saveは1件ずつ、saveAllは複数件まとめてデータベースに保存・更新するメソッドです。

JPAとJDBCの使い分けについては『【保存版】JPA vs JDBC│Spring Bootでの実務的な使い分けと導入判断ガイド』をご参照ください。

サンプルで使うUserエンティティとRepository

// User.java
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String status;
    private int point;
    // getter / setter ...
}
// UserRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
    public interface UserRepository extends JpaRepository<User, Long> {
}

データベースには次のようなデータが入っていると仮定します:

用語解説:Entity(エンティティ)
データベースのテーブル構造をJavaクラスで表現したもの。@Entityアノテーションで定義します。

用語解説:Repository(リポジトリ)
データベース操作(保存・取得・更新)を担当するインターフェース。Spring Data JPAで自動生成されます。

List型やMap型の違いについては『【Spring Boot実践ガイド】List型・Map型の違いとBeanクラスの基礎&応用』も参考になります。

id name status point
1 Alice ACTIVE 100
2 Bob INACTIVE 50
3 Carol ACTIVE 70

save(1件ずつ更新)

List<User> userList = userRepository.findAll();
for (User user : userList) {
    if ("ACTIVE".equals(user.getStatus())) {
        user.setPoint(user.getPoint() + 10);
        userRepository.save(user); // 1件ごとに更新
    }
}

特徴

  • 単純でわかりやすい。
  • 更新件数が多いとSQLが1件ずつ発行され、パフォーマンス低下の原因になる。

saveAll(まとめて更新)

List<User> userList = userRepository.findAll();
List<User> updatedUsers = userList.stream()
        .peek(user -> {
            if ("ACTIVE".equals(user.getStatus())) {
                user.setPoint(user.getPoint() + 10);
            }
        })
        .toList();
userRepository.saveAll(updatedUsers); // 一括更新

特徴

  • 複数件を一括で更新できるため効率的。
  • 内部的にはバッチ的にINSERT/UPDATEが発行される。
  • 大量更新時に有効。

処理前後のイメージ

処理前:

id name status point
1 Alice ACTIVE 100
2 Bob INACTIVE 50
3 Carol ACTIVE 70

処理後:

id name status point
1 Alice ACTIVE 110
2 Bob INACTIVE 50
3 Carol ACTIVE 80

使い分けの目安

  • 少量更新 or 条件が複雑save
  • 大量更新 or 単純な変更処理saveAll

よくあるエラーと回避方法

ConcurrentModificationException

ループ中に直接削除すると発生。 → Iterator.remove()を利用して回避。

用語解説:Iterator(イテレータ)
コレクションの要素を順番に取り出すための仕組み。removeメソッドで安全に要素削除ができます。

nullチェック・Optional活用

APIレスポンスやDB取得値がnullになるケースではOptional.ofNullable()Objects.nonNull()で安全に処理する。

用語解説:Optional(オプショナル)
null値による例外を防ぐためのラッパークラス。値が存在するかどうかを安全に判定できます。

トランザクション管理の注意点

savesaveAll@Transactionalスコープ内で呼ぶ必要があります。トランザクション外では変更が反映されません。

用語解説:@Transactional(トランザクショナル)
複数のデータベース操作をひとつの処理単位(トランザクション)としてまとめるためのアノテーション。途中でエラーが発生した場合、すべての変更を元に戻せます。

@Transactionalの詳細やロールバックについては『Spring Bootでロールバックされない原因とは?@Transactional完全ガイド』をご参照ください。


実務での応用とスキルアップ

コードレビューでの説明力

「なぜsaveAllを選んだのか」を説明できることはレビューで評価される要素です。

パフォーマンスを意識する習慣

更新件数が数千件規模を超える場合、1件ずつsaveするとDB負荷が大きくなります。パフォーマンスを見据えた実装選択は信頼につながります。

基礎スキルがキャリアの土台に

List更新やsave / saveAllの使い分けといった基礎知識は、現場が変わっても通用する普遍的なスキルです。


FAQ

  • Spring BootでListをDBに更新するならsavesaveAllどちらを使うべき? → 少量ならsave、大量ならsaveAll

  • saveAllは内部でどんなSQLを発行しているの? → 複数のINSERT/UPDATEをまとめて発行。単一SQLにはならない。

  • saveで大量更新するとパフォーマンスが落ちるのはなぜ? → 1件ずつSQLが発行されるため。

  • saveAllを使ってもバルク更新SQLにはならないの? → デフォルトではならない。バルク処理が必要なら@Modifying+JPQLやネイティブクエリを利用。

  • トランザクションを付け忘れるとどうなる? → DBに変更が反映されない場合がある。@Transactionalを付与すべき。


結論

Spring BootにおけるListの更新は、for文・Stream・Iteratorを用いたループ処理に加えて、JPAのsave / saveAllを使ったDB反映をどう使い分けるかが重要です。少量の更新であればsave、大量の更新であればsaveAllが有効です。また、トランザクション管理を正しく行うことで、データ不整合や例外を未然に防げます。

最終的に大切なのは「なぜこの方法を選んだのか」を説明できること。これによりコードレビューやチーム開発での信頼が高まり、エンジニアとして一段上のステージに進むことができます。

もっとルートゼロを知りたいなら

DISCOVER MORE