Spring BootでListを1件ずつ更新する方法|初心者でもわかるサンプルコード付き
はじめに
Spring Bootで開発していると、Listの中身を1件ずつ更新したい場面にしばしば直面します。例えば、データベースから取得したユーザー情報を加工して再保存するときや、外部APIのレスポンスを整形するときです。しかし実際にコードを書く段階になると、「for文でループするのが最適なのか」「Streamで書けばスマートなのか」「更新時にsave
とsaveAll
をどう使い分けるべきか」と迷うことも多いのではないでしょうか。
本記事では、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では、エンティティの保存にsave
とsaveAll
が用意されています。
用語解説: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値による例外を防ぐためのラッパークラス。値が存在するかどうかを安全に判定できます。
トランザクション管理の注意点
save
やsaveAll
は@Transactional
スコープ内で呼ぶ必要があります。トランザクション外では変更が反映されません。
用語解説:@Transactional(トランザクショナル)
複数のデータベース操作をひとつの処理単位(トランザクション)としてまとめるためのアノテーション。途中でエラーが発生した場合、すべての変更を元に戻せます。
@Transactionalの詳細やロールバックについては『Spring Bootでロールバックされない原因とは?@Transactional完全ガイド』をご参照ください。
実務での応用とスキルアップ
コードレビューでの説明力
「なぜsaveAllを選んだのか」を説明できることはレビューで評価される要素です。
パフォーマンスを意識する習慣
更新件数が数千件規模を超える場合、1件ずつsave
するとDB負荷が大きくなります。パフォーマンスを見据えた実装選択は信頼につながります。
基礎スキルがキャリアの土台に
List
更新やsave
/ saveAll
の使い分けといった基礎知識は、現場が変わっても通用する普遍的なスキルです。
FAQ
-
Spring BootでListをDBに更新するなら
save
とsaveAll
どちらを使うべき? → 少量なら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
が有効です。また、トランザクション管理を正しく行うことで、データ不整合や例外を未然に防げます。
最終的に大切なのは「なぜこの方法を選んだのか」を説明できること。これによりコードレビューやチーム開発での信頼が高まり、エンジニアとして一段上のステージに進むことができます。