Javaのトランザクションとは、一連の処理を「すべて成功」か「すべて失敗」かのどちらかに保証する仕組みです。この記事を読めば、以下の内容がわかります。
「あれ、データベースにデータが登録されていない…」「更新したはずなのに、一部しか変わっていない…」。
Javaで開発をしていると、こんな経験はありませんか?私自身、駆け出しの頃にデータの不整合で頭を抱え、トランザクションの考慮漏れで深夜に緊急対応をした苦い経験があります。
過去の私と同じようにJavaのトランザクションで悩んでいる方に向けて、仕組みから実装まで解説します(2026年最新の実装パターンに基づいて解説します)。
- Javaにおけるトランザクションの基本的な仕組み
- JDBCやSpring Frameworkを使った具体的な実装方法
- よくある失敗例と、それを未然に防ぐための対策
Javaのトランザクションをマスターして、データの整合性を保つ堅牢なアプリケーションを開発しましょう。
Javaのトランザクションとは?基本の仕組みをわかりやすく解説

Javaのトランザクションは、一言でいうと「関連する一連の処理を、すべて成功させるか、すべて失敗させるか、どちらかに保証する仕組み」です。
途中で処理が失敗した場合、処理が開始される前の状態にきれいに戻してくれます。
トランザクションの定義と役割
例えば、銀行の振り込み処理を考えてみましょう。
- Aさんの口座から10,000円を引き出す
- Bさんの口座に10,000円を入金する
この2つの処理は、必ずセットで成功しなければなりません。もし1の処理は成功したのに、2の処理でシステムエラーが発生したらどうなるでしょうか。
Aさんの口座から10,000円が消え、Bさんの口座には入金されない、という最悪の事態が発生します。
このような事態を防ぐのがトランザクションの役割です。
「Aさんの出金」と「Bさんの入金」を1つのトランザクションとしてまとめることで、どちらかの処理が失敗すれば、両方の処理がなかったことになります。
これにより、データの整合性が保たれるのです。
データベースとの関係
Javaにおけるトランザクションは、データベース(DB)と密接に関連しています。
データベースを操作する言語であるSQLには、トランザクションを制御するための命令があります。
- COMMIT(コミット): トランザクション内のすべての処理を確定させる命令。
- ROLLBACK(ロールバック): トランザクション内のすべての処理を取り消し、開始前の状態に戻す命令。
Javaのプログラムは、これらの命令を適切なタイミングでデータベースに送ることで、トランザクションを管理しているのです。
ACID特性(Atomicity/Consistency/Isolation/Durability)とは
信頼性の高いトランザクション処理を実現するために、満たすべき4つの性質があり、それぞれの頭文字をとって「ACID特性」と呼ばれています。
| 特性 | 英語 | 意味 |
| 原子性 (A) | Atomicity | トランザクション内の処理が「すべて実行される」か「一つも実行されない」かのどちらかであることが保証される。 |
| 一貫性 (C) | Consistency | トランザクションの前後で、データの整合性が保たれていることが保証される。 |
| 独立性 (I) | Isolation | 複数のトランザクションを同時に実行しても、互いに影響を与えず、あたかも一つずつ順番に実行されているかのように見えることが保証される。 |
| 永続性 (D) | Durability | 正常に完了したトランザクションの結果は、システム障害が発生しても失われないことが保証される。 |
これらの特性があるおかげで、私たちは安心してデータの更新処理をシステムに任せられるのです。
Javaでトランザクションを扱う方法

Javaでトランザクションを実装する代表的な方法は、JDBCによる手動制御とSpring Frameworkの@Transactionalによる宣言的管理の2つです。
自動コミット(オートコミット)モードとは?デフォルト動作と手動制御との違い
JDBCのConnectionはデフォルトで自動コミットモード(getAutoCommit()がtrue)になっており、SQL文を1つ実行するたびに自動でコミットされます。
この状態では、複数の処理をまとめて「すべて成功かすべて失敗か」に制御することはできません。
| モード | メリット | デメリット |
|---|---|---|
| 自動コミット(デフォルト) | コードが簡潔、単発クエリに向く | 複数操作の整合性が保証されない |
手動制御(setAutoCommit(false)) | 複数操作をまとめて管理でき整合性を保てる | try-catch-finallyの記述が増える |
JDBCでのトランザクション処理の書き方(commitとrollback)
JDBC(Java Database Connectivity)は、Javaからデータベースに接続するための標準APIです。
JDBCを使うと、トランザクションを細かく手動で制御できます。
基本的な流れは以下の通りです。
- 自動コミットモードを無効にする (
con.setAutoCommit(false);) - 一連のSQL処理を実行する
- すべての処理が成功したら、コミットする (
con.commit();) - 処理中に例外が発生したら、ロールバックする (
con.rollback();)
手動での管理は柔軟性が高い反面、try-catch-finally句を使った定型的なコードが増え、記述が煩雑になりやすいデメリットもあります。
なお、実務ではコネクションプール(HikariCP等)を介したDataSourceから接続を取得するのが一般的です。
DriverManager.getConnectionの直接利用は学習用途に留め、本番環境ではSpring Bootのデフォルト実装であるHikariCPの利用を検討してください。
Spring Frameworkでのトランザクション制御
2026年現在のJava開発で主流となっているのが、Spring Framework(特にSpring Boot)を使ったトランザクション管理です。
Springを使うと、JDBCで必要だった煩雑なコードの多くをフレームワークが肩代わりしてくれます。
Springは「宣言的トランザクション管理」という仕組みを提供しており、開発者はビジネスロジックに集中できるのが大きなメリットです。
アノテーション(@Transactional)の使い方と注意点
Springで最も手軽にトランザクションを実装する方法が、@Transactionalアノテーションです。
このアノテーションをトランザクション管理したいメソッドに付与するだけで、フレームワークが自動でトランザクションの開始からコミット、ロールバックまでを制御してくれます。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(User user) {
// このメソッド内の処理はすべて1つのトランザクションになる
userRepository.save(user);
}
}非常に便利な@Transactionalですが、いくつか注意点があります。
実際にこの3つの注意点のうち、私が本番環境で踏んだのは「publicメソッド以外の呼び出し経路」の問題でした(詳細は後述の「トランザクションがコミットされない原因」で解説します)。
Javaトランザクションの種類とスコープ

トランザクションには、その適用範囲や管理方法によっていくつかの種類が存在します。
ローカルトランザクションとグローバルトランザクションの違い
- ローカルトランザクション: 1つのデータベースリソース内でのみ有効なトランザクションです。これまで説明してきたJDBCやSpringの基本的なトランザクションは、このローカルトランザクションにあたります。
- グローバルトランザクション: 複数のデータベースや、メッセージキューなど、複数のリソースをまたいでトランザクションを管理する仕組みです。分散トランザクションとも呼ばれます。
通常、アプリケーション開発ではローカルトランザクションで十分なケースがほとんどです。
プログラマティックトランザクションとデクララティブトランザクション
- プログラマティックトランザクション: JDBCの例のように、
commit()やrollback()をコード内に明示的に記述する方法です。 - デクララティブトランザクション: Springの
@Transactionalのように、アノテーションや設定ファイルでトランザクションを宣言する方法です。コードとトランザクション設定を分離できるため、可読性や保守性が向上します。
特別な理由がない限り、可読性の高いデクララティブトランザクションを選択するのが一般的です。
分散トランザクション(JTA)の概要
分散トランザクションを実現するためのJava標準仕様がJTA(Java Transaction API)です。
例えば、「データベースAに注文情報を登録し、データベースBの在庫情報を更新し、メッセージキューCに配送指示を送る」という一連の処理を、1つのトランザクションとして扱いたい場合などに利用されます。
なお、Jakarta EE 9(2021年)以降はJava EEからJakarta EEへの移行に伴い、パッケージ名がjavax.transactionからjakarta.transactionに変更され、正式名称も「Jakarta Transactions」に改称されています。
実装は複雑になりますが、大規模なエンタープライズシステムでは必須の技術です。
トランザクション制御でよくある失敗と対策
Javaのトランザクション制御で初心者が陥りやすい失敗は、設定ミス・意図しないロールバック・@Transactionalの適用漏れの3パターンに集約されます。
トランザクションがコミットされない原因
私自身、実際に本番環境でこの「同じクラス内の別メソッドから呼び出している」パターンを踏んだ経験があります。
サービスクラス内の別メソッドから@Transactionalを付けたメソッドを呼び出していたため、プロキシを経由せずトランザクションが一切効いていませんでした。
データの不整合が発生して初めてこの問題に気づき、以降はトランザクション境界を持つメソッドを別のサービスクラスに分割することを徹底するようになりました。
例外処理とロールバックの関係
最も多い失敗が、例外処理とロールバックの関係の誤解です。
@Transactional
public void updateUser(User user) {
try {
// 何らかのDB更新処理
update(user);
} catch (Exception e) {
// 例外を握りつぶしてしまうとロールバックされない!
log.error("エラーが発生しました", e);
}
}このコードでは、updateメソッドでRuntimeExceptionが発生しても、catchブロックで例外が捕捉されてしまうため、updateUserメソッドは正常に終了したとみなされます。
結果として、Springはロールバックを行わず、中途半端なデータがコミットされてしまう可能性があるのです。
例外を捕捉した場合は、再度RuntimeExceptionをスローするか、TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();を呼び出して明示的にロールバック対象に設定してください。
複数DBを扱う場合の注意点
1つのアプリケーションで複数のデータベースに接続する場合、注意が必要です。
それぞれのデータベース接続ごとにトランザクションが管理されるため、単一の@Transactionalでは両方のデータベースをまたいだトランザクション制御はできません。
この場合は、JTAを利用した分散トランザクションを検討するか、各データベースへの処理を別々のトランザクションメソッドに分割するなどの設計上の工夫が必要です。
実践で学ぶ!Javaトランザクションのサンプルコード

ここでは、JDBCの基本実装・Spring Bootでの@Transactional実装・rollbackFor設定の3パターンをサンプルコードで解説します。
JDBCを使った基本的なトランザクション処理例
// Java 7以降で標準のtry-with-resources構文でリソースを自動クローズ
try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password")) {
// 1. 自動コミットの無効化
conn.setAutoCommit(false);
try (PreparedStatement pstmt1 = conn.prepareStatement("UPDATE accounts SET balance = balance - 10000 WHERE id = 'A'");
PreparedStatement pstmt2 = conn.prepareStatement("UPDATE accounts SET balance = balance + 10000 WHERE id = 'B'")) {
// 2. 処理1: 口座Aから残高を減らす
pstmt1.executeUpdate();
// 3. 処理2: 口座Bに残高を増やす
pstmt2.executeUpdate();
// 4. すべて成功したらコミット
conn.commit();
System.out.println("処理が正常に完了しました。");
} catch (SQLException e) {
// 5. 例外発生時はロールバック
conn.rollback();
System.out.println("エラーが発生したため、処理をロールバックしました。");
e.printStackTrace();
}
} catch (SQLException e) {
e.printStackTrace();
}
// try-with-resourcesによりconn・pstmt1・pstmt2は自動的にcloseされるため、finallyでの手動close処理は不要Spring Bootでの@Transactional実装例
JDBCの例と比較すると、コードが非常にシンプルになるのがわかります。
// Serviceクラス
@Service
public class TransferService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transfer(String fromAccountId, String toAccountId, int amount) {
// 口座Aから残高を減らす
accountRepository.withdraw(fromAccountId, amount);
// ★ここでエラーが発生すると、withdrawの処理もロールバックされる
if (true) {
throw new RuntimeException("システムエラーが発生しました");
}
// 口座Bに残高を増やす
accountRepository.deposit(toAccountId, amount);
}
}transferメソッドに@Transactionalが付いているため、メソッド内でRuntimeExceptionが発生すると、それまでに行ったDB操作(この場合はwithdraw)は自動的にロールバックされます。
失敗時にrollbackを自動化する設定例
特定のチェック例外が発生した場合にもロールバックさせたい場合は、rollbackFor属性を指定します。
@Service
public class OrderService {
@Autowired
private ProductRepository productRepository;
// 在庫不足例外(チェック例外)でもロールバックさせる
@Transactional(rollbackFor = StockShortageException.class)
public void placeOrder(long productId, int quantity) throws StockShortageException {
// 在庫チェック
Product product = productRepository.findById(productId);
if (product.getStock() < quantity) {
throw new StockShortageException("在庫が不足しています。");
}
// 在庫を減らす処理...
productRepository.decreaseStock(productId, quantity);
}
}トランザクション設計のベストプラクティス

効果的なトランザクション設計のポイントは、ビジネスロジック単位で境界を区切り、なるべく短く保つことです。
ビジネスロジックとトランザクション境界の設計
トランザクションの範囲(境界)は、どこからどこまでにすれば良いのでしょうか。
原則として、「一連のまとまりとして意味のあるビジネスロジック」を1つのトランザクション単位とします。
例えば、「ユーザー登録」であれば、usersテーブルへのインサートと、profilesテーブルへのインサートを1つのトランザクションにまとめるべきです。
また、トランザクションはなるべく短く保つことが重要です。
トランザクションが長いと、データベースのロック時間が長くなり、他の処理のパフォーマンスに影響を与える可能性があります。
パフォーマンスと整合性のバランスを取る方法
トランザクションには「分離レベル(Isolation Level)」という設定があります。
分離レベルとは、複数のトランザクションを同時に実行した際の、データの独立性をどのレベルで保証するかを定義するものです。
分離レベルを高くするとデータの整合性は強固になりますが、その分ロックが厳しくなりパフォーマンスが低下する傾向があります。
多くのケースでは標準的な分離レベルで十分ですが、システムの要件に応じて適切な分離レベルを選ぶことが大切です。
テスト時にトランザクションを扱うポイント
Springでは、テストコードに@Transactionalを付与すると、テストメソッドの実行後に自動でデータがロールバックされる便利な機能があります。
@SpringBootTest
@Transactional // テスト後のデータを自動でロールバック
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void testCreateUser() {
// テスト用のユーザーを作成
User newUser = new User("test-user");
userService.createUser(newUser);
// DBに登録されたか検証...
}
// このメソッドが終了すると、登録したユーザーデータは自動で削除される
}この機能を使えば、テストのたびにテストデータをクリーンアップする手間が省け、効率的にテストを実施できます。
よくある質問
Q1. Javaのトランザクションとは何ですか?
A1. 一連の処理を「すべて成功」か「すべて失敗」かのどちらかに保証する仕組みです。途中で処理が失敗した場合、処理が開始される前の状態にロールバックされ、データの整合性が保たれます。
Q2. commitとrollbackの違いは何ですか?
A2. commitはトランザクション内のすべての処理を確定させる命令、rollbackはすべての処理を取り消して開始前の状態に戻す命令です。処理が成功したらcommit、例外が発生したらrollbackを呼び出します。
Q3. @Transactionalが効かないのはなぜですか?
A3. 主な原因は3つです。(1)publicメソッドに付与していない、(2)同じクラス内の別メソッドから呼び出しプロキシを経由していない、(3)例外を握りつぶしてSpringが検知できていない、のいずれかが多いケースです。
Q4. トランザクションがコミットされないのはなぜですか?
A4. DataSourceやトランザクションマネージャーの設定ミス、意図しない例外発生によるロールバック、@Transactionalが適用されない呼び出し方の3つが典型的な原因です。ログを確認することで多くの場合は特定できます。
Q5. JDBCとSpringのどちらでトランザクションを実装すべきですか?
A5. 現在のJava開発ではSpring Frameworkの@Transactionalが主流です。JDBCの直接操作より記述量が少なく、宣言的にトランザクション管理ができるため、特別な理由がない限りSpringでの実装を選んでください。
Q6. 分散トランザクション(JTA)とは何ですか?
A6. 複数のデータベースやメッセージキューなど、複数のリソースをまたいでトランザクションを管理する仕組みです。Java標準仕様のJTA(Jakarta EE 9以降はJakarta Transactions)で実現します。
Q7. トランザクションの分離レベルとは何ですか?
A7. 複数のトランザクションを同時に実行した際に、データの独立性をどのレベルで保証するかを定義する設定です。レベルを高くすると整合性は強固になりますが、その分パフォーマンスが低下する傾向があります。
まとめ|Javaのトランザクションを理解して堅牢なアプリを作ろう
Javaのトランザクションは、ACID特性の理解とcommit/rollbackの正しい制御によって、データ不整合を防ぐ仕組みです。
本記事で学んだポイントの振り返り
- トランザクションは、一連の処理の整合性を保つための重要な仕組みである。
- 基本となる性質は「ACID特性」(原子性、一貫性、独立性、永続性)。
- Javaでは主にJDBCやSpring Frameworkを使って実装する。
- Springの
@Transactionalは非常に強力だが、ロールバックの条件など注意点も存在する。 - トランザクションの範囲はビジネスロジックの単位で設計し、短く保つことが理想。
初心者がつまずきやすい部分と学習のコツ
初心者が最もつまずきやすいのは、やはり「@Transactionalを付けたのにロールバックされない」という問題です。
この原因のほとんどは、例外処理の誤解や、アノテーションが適用されない呼び出し方をしていることにあります。
学習のコツは、実際に簡単なサンプルコードを書いてみることです。
デバッガで動きを追いながら「どのタイミングで例外が投げられるか」「どの時点でロールバック処理が呼ばれるか」を自分の目で確認しましょう。



