トランザクション処理を実装してみましょう。
SpringBootではどうやればいいのでしょうね。
トランザクション処理は@Transactional
アノテーションで
例えばこんな感じの実装の時、insert処理に対してトランザクションをかけたいとします。
(もし全部のソースコードみたい方は最後の方にgitlabのURL貼ってますのでそちらからどうぞ)
// IndexController.java
@Controller
public class IndexController {
:
@RequestMapping
public String index(Model model) {
// do something,
}
@RequestMapping(value = "/insert")
public String insert(Model model, @RequestParam("name") String name) {
// do something.
}
:
}
以下のように insertメソッドの前に @Transactional
アノテーションを追加するだけです。
あ、import org.springframework.transaction.annotation.Transactional;
も必要ですね。
// IndexController.java
import org.springframework.transaction.annotation.Transactional; // ←これと
@Controller
public class IndexController {
:
@RequestMapping
public String index(Model model) {
// do something,
}
@Transactional // ← これを追加!
@RequestMapping(value = "/insert")
public String insert(Model model, @RequestParam("name") String name) {
// do something.
}
:
}
これでinsertメソッド中の実行中に例外が発生すると自動的にロールバックをしてくれます。簡単ですね。
何か気をつけることはあるのかな。
ロールバックしない例外がある
ただし、ロールバックの注意点として、非検査例外(RuntimeException及びそのサブクラス)が発生した場合はロールバックされるが、検査例外(Exception及びそのサブクラスでRuntimeExceptionのサブクラスじゃないもの)が発生した場合はロールバックされずコミットされる。
via: Springでトランザクション管理
ほう。試してみましょう。例外の制御をしやすいのでupdate処理に例外処理をいれます。デモページの[果物の名前を変更する > 変更後] のテキストエリアに非検査例外
、検査例外
と入力すると、それぞれの例外をスローする処理を追加しました。
// IndexController.java
@Controller
public class IndexController {
:
@Transactional
@RequestMapping(value = "/update")
public String insert(Model model, @RequestParam("chg_before_name") String before_name,
@RequestParam("chg_after_name") String after_name) throws Exception {
fruitMapper.update(before_name, after_name);
List<Fruit> list = fruitMapper.selectAll();
model.addAttribute("fruits", list);
// トランザクション動作確認
if(after_name.equals("非検査例外")) {
// 非検査例外の時にはロールバックが発生します。
System.out.println("非検査例外発生");
throw new RuntimeException();
}
if(after_name.equals("検査例外")) {
// 検査例外の時にはそのままコミットされます。
System.out.println("検査例外発生");
throw new Exception();
}
return "index";
}
// 例外キャッチ
@ControllerAdvice
public class ExceptionHandlerAdvisor {
@ExceptionHandler
public String catchError(RuntimeException e){
System.out.println("非検査例外キャッチ");
return "error";
}
@ExceptionHandler
public String catchError(Exception e){
System.out.println("検査例外キャッチ");
return "error";
}
}
:
}
動作確認
非検査例外、検査例外それぞれの動きを確認してみましょう。
非検査例外のとき
mysql> select * from fruits;
+----+-----------+
| id | name |
+----+-----------+
| 1 | みかん |
| 2 | ばなな |
| 3 | りんご |
+----+-----------+
3 rows in set (0.00 sec)
-- アップデート実行するが、非検査例外である RuntimeException がスローされる。
mysql> select * from fruits;
+----+-----------+
| id | name |
+----+-----------+
| 1 | みかん |
| 2 | ばなな |
| 3 | りんご |
+----+-----------+
3 rows in set (0.01 sec)
みかん
が非検査例外
に更新されるかと思いますが、トランザクション処理中の例外発生によりロールバックされています。つまり、更新処理はなかったことにされていますね。
検査例外のとき
mysql> select * from fruits;
+----+-----------+
| id | name |
+----+-----------+
| 1 | みかん |
| 2 | ばなな |
| 3 | りんご |
+----+-----------+
3 rows in set (0.00 sec)
-- アップデート実行するが、検査例外である Exception がスローされる。
mysql> select * from fruits;
+----+--------------+
| id | name |
+----+--------------+
| 1 | 検査例外 |
| 2 | ばなな |
| 3 | りんご |
+----+--------------+
3 rows in set (0.00 sec)
非検査例外の時と同じように "みかん" が "検査例外" に更新されないことを期待していましたが、今回スローした例外は検査例外
のためコミットされてしまいました。
こういう動きがあることを知っておかないと危ないですね。
実際に動かしてみたい方
自分のSpringBootプロジェクト環境を仲間に渡そう を参考に、以下のリポジトリをクローンしてもらえれば動作すると思います。
# STSプロジェクトを入手します
git clone -b demo_transaction https://gitlab.com/msrx9/sample_springboot_crud.git
# MySQL環境を入手します
git clone https://gitlab.com/msrx9/sample_docker_mysql.git