トランザクションとは
「Oracle Database」の単元でも少し触れましたが、トランザクションとは処理のまとまりのことです。
トランザクションを開始して、コミット(確定)されるまでをひとまとまりとして処理します。
エラーが発生したり、例外が投げられた場合には、処理をなかったことにして、トランザクションの開始前に戻します。(ロールバック)
<トランザクションを使用すべき例>
例えば、口座Aから口座Bに1万円を移動する場合
(1) 口座A:1万円 残高が減る
(2) 口座B:1万円 残高が増える
これらの処理の片方だけが実行されてもう片方が失敗したら、困ったことになってしまいます。
こういった不整合を防ぐために「まとめて処理を反映」させたいのです。
トランザクションを機能させていない場合、データの登録や更新処理の途中で失敗したときに、成功した一部分だけ処理が行われてしまいます。
このように複数のデータ更新処理を、「すべて実行するか」「すべて行わないか」の二者択一にしてくれるのがトランザクションです。
Transactionの使い方
Springでは、@Transactionalというアノテーションを付けるだけでTransactionを適用してくれます。
具体例で見ていきましょう。
@Transactionalを使用したサンプルコード
Controllerで呼び出すためのServiceメソッドを作成します。
【com/cmps/spring/repository/service/EmployeeService.java】に追記
import org.springframework.transaction.annotation.Transactional;////追記
@Service
@Transactional(rollbackFor = Exception.class)////追記
public class EmployeeService {
/**
* トランザクション確認用のサンプル処理
* idでEntityを特定し削除 + サンプルEntityを保存
*
* @param delId
* @return
*/
public void doTransactionSample(int delId) {
// delIdを元にEntityを取得し、削除する
Employee employee1 = employeeRepository.findById(delId).get();//※①Entityが存在しない場合、エラーになる
employeeRepository.delete(employee1);
//用意したインスタンスをDBに新規登録
Employee employee2 = new Employee("00800", "日下部", 46);//※②codeカラムは4文字が上限のため、"00800"の場合SQL実行時に必ずエラーになる
employeeRepository.save(employee2);
}
}
・@TransactionalアノテーションをServiceクラス自体に付与しています。
このように付与することでSeviceクラス内の全メソッド(正確には全publicメソッド、詳細は後述)に対してトランザクションが機能します。
なお、@Transactionalはメソッドごとに付けることもできます。
////例
@Service
public class EmployeeService {
@Transactional(rollbackFor = Exception.class)////
public void doTransactionSample(int delId) {
//以下略
・@Transactionalに対し、(rollbackFor = Exception.class)とプロパティを指定していますが、これはロールバックの条件を指定しています。
@TransactionalはデフォルトではRuntimeException(非検査例外)のみを対象にしているため
検査例外も含めたい場合はこのように上位のExceptionクラスを指定します。迷ったらこの記述にしておくと無難です。
・「※①~」、「※②~」に書いてある通り、例外が発生し得るようなコードにしております。
少なくともいずれかで例外が発生すれば、ロールバック=どちらの処理も実行されません。
上記のメソッドを呼び出す記述をControllerに追加します。
【com/cmps/spring/controller/EmployeeController.java】に追記
@RequestMapping("/emp")
@Controller
public class EmployeeController {
/**
* トランザクション確認用のサンプル処理
* @param model Model
* @return "redirect:/emp/all" 一覧表示へリダイレクト
*/
@GetMapping("/transaction")
public String doTransaction(Model model) {
try {
employeeService.doTransactionSample(111);
} catch (Exception e) {
e.printStackTrace();
}
return "redirect:/emp/all";
}
}
・Controllerでは@Transactionalの記述はありません。@Transactionalが付与されたServiceメソッドを呼び出し、メソッドをtry~catch文で囲んで例外処理を行っています。
try~catch文を記述しない場合、ブラウザにエラー画面が出力されてしまうため、try~catch文の記述も必須です。
上記のコードの記述後、/emp/transactionにアクセスすると
ServiceクラスのdoTransactionSampleメソッドに記述した、データの削除・追加 どちらの処理も行われていないことが確認できるはずです。
これはトランザクションが正常に働き、処理がロールバックされたということです。
反対に、問題なく処理が実行できるように記述を書き換えて再度アクセスしてみると、どちらの処理も実行されていること=コミットが確認できるでしょう。
@Transactional使用上の注意点
使用するにあたっていくつかの注意点があります。
- publicメソッドに対して有効(public以外のメソッドには機能しない)
- Controllerには付与しない。Seviceメソッドに付与する。
- Controller側でtry~catch文で囲まれていること。
- 直接呼び出されるメソッドに付与すること。
ServiceメソッドAから別のメソッドBを呼ぶ場合、Aにトランザクションを付与しても、Bに付与していなければBには機能しない。 - 継承されたメソッドの場合、親クラスにも子クラスにも@Transactionalを付与する必要がある。
Serviceインターフェースを作成している場合にもこれに当てはまります。
参考:
Springでトランザクション管理 – Qiita @Transactionalに設定できるプロパティを見たい場合はこちら
Spring Bootのトランザクションが効かない場合のチェック項目
詳解Springトランザクション -初級から上級まで- #jsug | ドクセル
練習問題
問1:トランザクションを使用して以下の複数データをテーブルに登録してください。
「輸出先」テーブル
| export_code | export_name | population | region |
|---|---|---|---|
| 15 | パローヌ国 | 200 | 中部 |
| 22 | トカンタ国 | 150 | 北洋 |
| 23 | アルファ帝国 | 120 | 北洋 |
| 25 | リトール王国 | 150 | 南洋 |
| 31 | タハル王国 | 240 | 北洋 |
| 32 | サザンナ王国 | 80 | 南洋 |
| 33 | マリヨン国 | 300 | 中部 |
