| この記事の要点 |
|
@Transactional とは
@Transactional は、Spring Framework で DB を更新する際のトランザクションを管理するアノテーションです。付けたメソッドの開始時にトランザクションを開始し、正常に終わればコミット、例外が投げられればロールバックします。commit() や rollback() を自分で書かずに、「このメソッドの中の DB 操作はすべて成功するか、すべて取り消されるか」を保証できます。
クラスもしくはメソッド単位で付与することができます。クラスに付けるとそのクラスの public メソッドすべてに適用され、クラスとメソッドの両方に付与した場合はメソッドのアノテーションの設定が優先されます。
Spring Boot では、spring-boot-starter-data-jpa や spring-boot-starter-jdbc を依存に加えるとトランザクション管理が自動で有効になるため、@EnableTransactionManagement を自分で書く必要は通常ありません。
基本的な使い方
一般的には、業務ロジックをまとめる Service クラスのメソッドに付けます。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TransferService {
private final AccountRepository accountRepository;
public TransferService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
@Transactional
public void transfer(Long fromId, Long toId, long amount) {
Account from = accountRepository.findById(fromId).orElseThrow();
Account to = accountRepository.findById(toId).orElseThrow();
from.withdraw(amount); // 残高不足なら RuntimeException
to.deposit(amount);
// 例外が出れば、両方の変更がロールバックされる
}
}
次は JpaRepository で、更新系のクエリメソッドに @Transactional を使用した例です。@Modifying 付きの更新クエリはトランザクション内で実行する必要があります。
@Repository
public interface TestRepository extends JpaRepository<TestEntity, String> {
@Transactional
@Modifying
@Query("UPDATE TestEntity te SET te.colA = 1 WHERE te.id = :id")
Integer updateTest(@Param("id") String id);
}
なお、save() や findById() など Spring Data JPA が標準で提供するメソッドには、最初からトランザクション設定が付いています。
主なオプション
| 属性 | 既定値 | 意味 |
|---|---|---|
| propagation | REQUIRED | 既にトランザクションがあるときの振る舞い(下表) |
| isolation | DEFAULT | 分離レベル。DEFAULT は DB の既定に従う(MySQL InnoDB なら REPEATABLE READ) |
| readOnly | false | 読み取り専用のヒント。JPA ではフラッシュを省くなどの最適化が効く |
| timeout | -1(無制限) | タイムアウト秒数 |
| rollbackFor | なし | 追加でロールバック対象にする例外クラス |
| noRollbackFor | なし | ロールバックしない例外クラス |
| transactionManager(value) | 既定のもの | 複数 DB を使うときに使うトランザクションマネージャを指定 |
propagation(伝播)の種類
| 値 | 既存トランザクションがある場合 | ない場合 |
|---|---|---|
| REQUIRED | それに参加する | 新しく開始する |
| REQUIRES_NEW | 既存を一時停止して、別の新しいトランザクションを開始する | 新しく開始する |
| NESTED | セーブポイントを作って入れ子で実行する | 新しく開始する |
| SUPPORTS | 参加する | トランザクションなしで実行する |
| MANDATORY | 参加する | 例外を投げる |
| NOT_SUPPORTED | 既存を一時停止してトランザクションなしで実行する | トランザクションなしで実行する |
| NEVER | 例外を投げる | トランザクションなしで実行する |
REQUIRES_NEW は「本処理が失敗しても操作ログだけは必ず残したい」といった場面で使います。
ロールバックされる例外・されない例外
既定では、RuntimeException(非チェック例外)と Error ではロールバックし、チェック例外(IOException など)ではコミットします。チェック例外でもロールバックしたい場合は次のように指定します。
@Transactional(rollbackFor = Exception.class)
public void importCsv(Path file) throws IOException {
// IOException が出てもロールバックされる
}
@Transactional(readOnly = true)
public List<User> findActiveUsers() {
return userRepository.findByActiveTrue();
}
Spring Framework 6.2 以降では、@EnableTransactionManagement(rollbackOn = RollbackOn.ALL_EXCEPTIONS) でアプリ全体の既定を「すべての例外でロールバック」に変えることもできます。
効かないときの落とし穴
- 同じクラス内のメソッドから呼んでいる: @Transactional は Spring が作るプロキシを経由した呼び出しにだけ効く。
this.save()のような自己呼び出しではプロキシを通らないため、トランザクションが開始されない。別の Bean に切り出すのが基本の対処 - Spring 管理外のオブジェクト:
newで作ったインスタンスのメソッドには効かない - 例外を握りつぶしている: メソッド内で catch して外に投げないと、正常終了とみなされてコミットされる
- private メソッド: プロキシから呼べないため効かない。Spring 6 以降は、クラスベースのプロキシなら protected やパッケージプライベートのメソッドにも適用される
- アノテーションの取り違え:
jakarta.transaction.Transactionalも Spring で動くが、readOnly・isolation・timeout などの属性はorg.springframework.transaction.annotation.Transactionalにしかない。import を確認する - 別スレッドでの処理: トランザクションはスレッドに紐づくため、
@Asyncや自前のスレッドで実行した処理は呼び出し元のトランザクションに含まれない
確認方法
トランザクションが開始・コミット・ロールバックされているかは、ログで確認するのが確実です。application.properties に次を追加します。
logging.level.org.springframework.transaction=DEBUG
logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG
メソッド呼び出し時に「Creating new transaction with name [...]」、終了時に「Committing」や「Rolling back」といったログが出れば、正しく適用されています。コード内で確認したい場合は TransactionSynchronizationManager.isActualTransactionActive() が true を返すかを見ます。
関連
- DB トランザクション完全ガイド(SQL BEGIN/COMMIT / Laravel / Spring / 分離レベル / デッドロック)
- SQL トランザクション制御 完全ガイド(BEGIN/COMMIT/ROLLBACK/SAVEPOINT/ACID/分離レベル)
- SQL SAVEPOINT の使い方 - 部分ロールバック・ネストトランザクション完全ガイド
- 【Spring】DB接続設定から値の取得まで(JdbcTemplate編)
- Hibernateとは|JavaのORMライブラリとJPA参照実装の基礎
子ページはありません
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- プロジェクトをTomcatプロジェクトとして認識させる方法 2026-10-07 22:32:50
- MySQLの1366 Incorrect string value|Laravelの文字コード・絵文字エラー 2026-10-07 21:54:03
- curlの証明書ホスト名不一致|旧エラー51・現行60の確認と対処 2026-10-07 21:54:03
- LaravelのMassAssignmentException|fillableの原因と安全な対処 2026-10-07 21:54:03
- Eclipse で Tomcat の起動ログがコンソールに出ない時の確認手順 2026-10-07 21:54:02
- MySQLにおける中央値(Median)の導き方(バージョン8未満) 2026-10-07 13:49:45
- getInputForward 2026-10-07 13:41:15
- JSONから配列に変換 2026-10-07 13:41:15
- ビューから値をモデルに格納しコントローラーで受け取る方法 2026-10-07 13:23:41
- Laravelのテーブル作成と定義変更|マイグレーション・up/down・注意点 2026-10-07 13:23:41
- NumPy 配列に要素を追加する方法 (append / concatenate) 2026-10-07 13:23:41
- MariaDB・MySQLで現在日時を取得する方法|NOW・タイムゾーン・保存型 2026-10-07 13:13:36
- 【django】テンプレートで定数を使用する方法 2026-10-07 13:10:15
- Spring BootにおけるApplication.propertiesの環境依存設定の分割方法 2026-10-07 12:09:35
- Not supported for DML operations【Springエラー】 2026-10-07 11:09:38