Springプログラマティックトランザクション管理:初心者向け実践ガイド
Springの@Transactionalアノテーションは宣言的なトランザクション管理に優れていますが、常に最良の選択ではありません。このJottingでは、トランザクションのライフサイクルを細かく管理する必要がある状況に最適な、Springでのプログラマティックなトランザクション制御をいつ、どのように使用するかを学びます。
なぜプログラマティックトランザクションが必要か?
コネクションプール枯渇問題
この一般的なシナリオを想像してください:データベース呼び出しと外部APIを組み合わせたメソッド:
@Transactional
public void processPayment(PaymentRequest request) {
savePaymentRequest(request); // DB
callPaymentProviderApi(request); // 外部API(遅い)
updatePaymentState(request); // DB
saveAuditHistory(request); // DB
}ここでの問題は何ですか?
Springが@Transactionalでトランザクションを作成すると:
- プールから接続を取得し、メソッド全体の間保持します
- 接続は占有されたまま、外部APIの応答を待ちます
- APIに5-10秒かかる場合、その間接続はブロックされます
- 高負荷下では、遅いAPIを待ってすべての利用可能な接続を枯渇させます
黄金律: 同じトランザクション内でデータベース操作と外部API呼び出しを混在させないでください。
解決策1:TransactionTemplate(推奨)
TransactionTemplateは、トランザクションを手動で管理するためのコールバックベースのAPIを提供します。これは、プログラマティックなトランザクションを扱う最もクリーンで現代的な方法です。
基本的な設定
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.stereotype.Component;
@Component
public class PaymentService {
private final TransactionTemplate transactionTemplate;
public PaymentService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
}注意: Spring Bootは自動的に
PlatformTransactionManagerを設定します。インジェクションするだけでよいです。
例1:戻り値を持つトランザクション
public Long createSuccessfulPayment(PaymentRequest request) {
// トランザクション内でコードを実行し、IDを返す
Long paymentId = transactionTemplate.execute(status -> {
Payment payment = new Payment();
payment.setAmount(request.getAmount());
payment.setReferenceNumber(request.getReference());
payment.setState(Payment.State.SUCCESSFUL);
entityManager.persist(payment);
// IDはpersist後に自動生成される
return payment.getId();
});
return paymentId;
}これは何をしますか?
- 自動的にトランザクションを作成
- ラムダコードをトランザクション内で実行
- すべてが順調に進めば、自動的にコミット
- 例外が発生すれば、自動的にロールバック
- ラムダからの値を返す
例2:例外時の自動ロールバック
public void createTwoPaymentsWithRollback() {
try {
transactionTemplate.execute(status -> {
Payment first = new Payment();
first.setReferenceNumber("REF-001");
first.setAmount(1000L);
entityManager.persist(first); // OK
Payment second = new Payment();
second.setReferenceNumber("REF-001"); // 重複!
second.setAmount(2000L);
entityManager.persist(second); // 例外をスロー
return null;
});
} catch (Exception e) {
// 最初の支払いも巻き戻される - 原子性が保証される
System.out.println("トランザクションがロールバックされました:" + e.getMessage());
}
}例3:明示的な手動ロールバック
public Long createPaymentWithValidation(PaymentRequest request) {
return transactionTemplate.execute(status -> {
Payment payment = new Payment();
payment.setReferenceNumber(request.getReference());
payment.setAmount(request.getAmount());
entityManager.persist(payment);
// カスタムビジネス検証
if (request.getAmount() > 100000) {
// ロールバック用にマーク - トランザクションは巻き戻される
status.setRollbackOnly();
return null; // またはカスタム例外をスロー
}
return payment.getId();
});
}例4:戻り値のないトランザクション
public void saveAuditLogEntry(AuditEntry entry) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
auditRepository.save(entry);
// 何も返さず、操作を実行するだけ
}
});
}例5:インスタンスごとのカスタム設定
異なる設定を持つ複数のTransactionTemplateインスタンスを作成できます:
@Component
public class TransactionConfig {
@Bean
public TransactionTemplate readOnlyTransactionTemplate(PlatformTransactionManager txManager) {
TransactionTemplate template = new TransactionTemplate(txManager);
template.setReadOnly(true); // クエリ用の最適化
return template;
}
@Bean
public TransactionTemplate serializableTransactionTemplate(PlatformTransactionManager txManager) {
TransactionTemplate template = new TransactionTemplate(txManager);
template.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
template.setTimeout(30); // 秒
return template;
}
@Bean
public TransactionTemplate requiresNewTransactionTemplate(PlatformTransactionManager txManager) {
TransactionTemplate template = new TransactionTemplate(txManager);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return template;
}
}利用可能な設定
| プロパティ | 値 | 説明 |
|---|---|---|
setIsolationLevel() | ISOLATION_READ_UNCOMMITTED | リードアンコミッティッド分離レベル |
ISOLATION_READ_COMMITTED | コミット済みデータのみ読み取り | |
ISOLATION_REPEATABLE_READ | 反復不能読み取りを防止 | |
ISOLATION_SERIALIZABLE | 最大分離 | |
setPropagationBehavior() | PROPAGATION_REQUIRED | 既存を使用するか、新規作成 |
PROPAGATION_REQUIRES_NEW | 常に新しいトランザクションを作成 | |
PROPAGATION_NESTED | ネストされたトランザクション(セーブポイント) | |
setTimeout() | 秒(int) | 最大実行時間 |
setReadOnly() | true/false | 読み取り専用の最適化 |
解決策2:PlatformTransactionManager(低レベル)
完全な制御が必要な場合は、PlatformTransactionManagerを直接使用します。これは、@TransactionalとTransactionTemplateの両方が内部的に使用するAPIです。
例:完全な手動制御
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Component
public class ManualTransactionService {
private final PlatformTransactionManager transactionManager;
public ManualTransactionService(PlatformTransactionManager txManager) {
this.transactionManager = txManager;
}
public void processWithTotalControl() {
// 1. トランザクション設定を定義
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
definition.setTimeout(5); // 5秒
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 2. トランザクションを開始
TransactionStatus status = transactionManager.getTransaction(definition);
try {
// 3. ビジネス操作を実行
Payment payment = new Payment();
payment.setAmount(500L);
payment.setReferenceNumber("MANUAL-001");
entityManager.persist(payment);
// 4. トランザクションをコミット
transactionManager.commit(status);
} catch (Exception ex) {
// 5. エラー時にロールバック
transactionManager.rollback(status);
throw new RuntimeException("手動トランザクションでエラー", ex);
}
}
}比較:TransactionTemplate vs PlatformTransactionManager
| 側面 | TransactionTemplate | PlatformTransactionManager |
|---|---|---|
| 抽象化レベル | 高(コールバック) | 低(手動) |
| エラー処理 | 自動 | 手動(try-catch) |
| 自動ロールバック | はい | いいえ(呼び出す必要がある) |
| 結果的なコード | よりクリーン | より冗長 |
| 柔軟性 | コールバックによる制限 | 完全な制御 |
| 推奨される用途 | ほとんどのケース | 細かい制御が必要な場合 |
実践的なケース:DBと外部APIの分離
元の問題(枯渇リスクあり):
@Transactional
public void processOrder(Order order) {
orderRepository.save(order); // DB
shippingApi.createShipment(order); // 遅いAPI
notificationService.notifyCustomer(order); // 遅いAPI
}TransactionTemplateを使用した解決策:
@Component
public class OrderService {
private final TransactionTemplate txTemplate;
private final OrderRepository orderRepository;
private final ShippingApiService shippingApi;
private final NotificationService notificationService;
public void processOrderSafely(Order order) {
// ステップ1:トランザクション内でのDB部分のみ
Long orderId = txTemplate.execute(status -> {
order.setStatus("PROCESSING");
orderRepository.save(order);
return order.getId();
});
// 接続はすでに解放されている!
// ステップ2:外部API(接続を占有しない)
String trackingNumber = shippingApi.createShipment(order);
// ステップ3:別の外部API(接続を占有しない)
notificationService.notifyCustomer(order);
// ステップ4:最終ステータスの更新(新しい短いトランザクション)
txTemplate.executeWithoutResult(status -> {
Order updated = orderRepository.findById(orderId).orElseThrow();
updated.setTrackingNumber(trackingNumber);
updated.setStatus("SHIPPED");
});
}
}各アプローチを使用するタイミング
@Transactional(宣言的)を使用する場合:
- 単純なCRUD操作
- 外部API呼び出しがない
- 複雑な条件付き制御が不要
- コードをクリーンで読みやすく保ちたい
TransactionTemplate(プログラマティック)を使用する場合:
- DB操作と外部I/Oを混在させる必要がある
- 条件付きロジックがロールバックを決定する
- 同じサービスで異なるトランザクション設定が必要
- トランザクションから値を返す必要がある
PlatformTransactionManagerを使用する場合:
- ライフサイクルの完全な制御が必要
- 1つのメソッドで複数のトランザクションが必要
- 非標準のトランザクションシステムとの統合
- 詳細なトランザクションロギングまたは監査
結論
プログラマティックトランザクションは、@Transactionalが提供できない制御を提供します。TransactionTemplateは、データベース操作を外部呼び出しから分離する必要があるほとんどのケースで、コネクションプールの枯渇を防ぐための最良の味方です。
覚えておいてください: 目標は
@Transactionalを置き換えることではなく、宣言的アプローチの制約が設計を制限する場合に補完することです。
参考文献と追加リソース
公式ドキュメント
- Spring Framework - Programmatic Transaction Management: Springチームによるプログラマティックトランザクション管理の完全ガイド。 docs.spring.io
- Spring Data Access Documentation: データアクセスとトランザクションに関する公式ドキュメント。 docs.spring.io
推奨記事
- Vlad Mihalcea - Spring Transaction and Connection Management: Springがデータベース接続とトランザクションをどのように処理するかについての深い分析。 vladmihalcea.com
- Baeldung - Programmatic Transaction Management in Spring: TransactionTemplateとPlatformTransactionManagerの実用的なチュートリアル。 baeldung.com
- Marco Behler - Spring Transaction Management @Transactional In-Depth: Springでのトランザクションの内部動作に関する詳細ガイド。 marcobehler.com
追加のベストプラクティス
- 接続プールで
auto-commit=falseを設定する - レイジー接続取得を有効にする - Hibernate使用時は
hibernate.connection.provider_disables_autocommit=trueを設定する DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTIONを検討する - 接続の再利用を最大化- サービス層を設計する - トランザクションメソッドは実行のできるだけ遅い段階で呼び出す