@Transactionalとは
@Transactionalは、複数のDB操作を1つのトランザクションとして実行するためのアノテーションです。
処理が正常に終了すればコミットされ、途中で例外が発生するとまとめてロールバックされます。
つまり、複数の更新処理を「全部成功」または「全部失敗」にするための仕組みです。
@Transactionalが守るもの
@Transactionalは、複数のDB操作の整合性を守ります。
例えば、2つの更新処理がある場合、
出金
↓
入金
の途中で例外が発生しても、出金だけが反映されることはありません。
○ 両方成功
× 両方失敗(ロールバック)
のどちらかになるため、データの不整合を防ぐことができます。
つまり@Transactionalが守るのは、処理全体の一貫性(整合性)です。
@Transactionalが守らないもの
@Transactionalは、同時実行による競合を防ぐものではありません。
例えば、口座残高が100円の状態で、2つの出金処理(80円)が同時に実行されたとします。
残高:100
A
残高取得 → 100
B
残高取得 → 100
A
80円出金
B
80円出金
AもBも「残高は100円ある」と判断して処理を進めるため、意図しない結果になることがあります。
@Transactionalを付けていても、複数のトランザクションが同じデータを読み取り、それぞれ更新を行うことは防げません。
つまり@Transactionalは、処理の整合性は守りますが、並行実行時の競合までは防ぎません。
並行実行の対策方法
並行実行による競合を防ぐには、@Transactionalとは別の対策が必要です。
代表的な方法として、以下があります。
- 更新条件をSQLに含める(Atomic Update)
- 楽観ロック(
@Version) - 悲観ロック(
PESSIMISTIC_WRITE)
例えば残高更新であれば、
UPDATE users
SET balance = balance - 80
WHERE id = 1
AND balance >= 80
のように、チェックと更新を1回のSQLで行う方法がよく利用されます。
どの方法を選ぶかは要件によって異なりますが、重要なのは @Transactionalだけでは並行実行時の競合は防げない という点です。
まとめ
@Transactionalは複数のDB操作を1つの処理として扱い、途中で失敗した場合にロールバックする仕組みです。一方で、並行実行による競合は防げないため、必要に応じてロックやAtomic Updateなどの対策を組み合わせる必要があります。