SpringBootでトランザクション処理を実装しよう

トランザクション処理を実装してみましょう。
SpringBootではどうやればいいのでしょうね。

## トランザクション処理は`@Transactional` アノテーションで

例えばこんな感じの実装の時、insert処理に対してトランザクションをかけたいとします。
(もし全部のソースコードみたい方は最後の方にgitlabのURL貼ってますのでそちらからどうぞ)

```java
// 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;` も必要ですね。

```java
// 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でトランザクション管理](https://qiita.com/NagaokaKenichi/items/a279857cc2d22a35d0dd)

ほう。試してみましょう。例外の制御をしやすいのでupdate処理に例外処理をいれます。デモページの[果物の名前を変更する > 変更後] のテキストエリアに`非検査例外`、`検査例外`と入力すると、それぞれの例外をスローする処理を追加しました。

<img width="1231" alt="tran1.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/27090/954bfb71-0129-4c85-36b1-e9d044029770.jpeg">

```java
// 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";
	     }
	}
  :
}
```
---

## 動作確認

非検査例外、検査例外それぞれの動きを確認してみましょう。

### 非検査例外のとき

![hi-kensa.gif](https://qiita-image-store.s3.amazonaws.com/0/27090/d5770e83-697a-2637-eed2-cb2981facf5d.gif)

```sql
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)

```
`みかん`が`非検査例外`に更新されるかと思いますが、トランザクション処理中の例外発生によりロールバックされています。つまり、更新処理はなかったことにされていますね。


### 検査例外のとき

![kensa.gif](https://qiita-image-store.s3.amazonaws.com/0/27090/058c2454-2bd2-2e63-44e8-3e885c398497.gif)

```sql
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プロジェクト環境を仲間に渡そう](https://qiita.com/msrx9/items/2cd129e5b039e2a101be) を参考に、以下のリポジトリをクローンしてもらえれば動作すると思います。

```
# 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
```

----

## 参考にさせてもらった記事

- [Spring Boot におけるトランザクション処理 (MyBatis/MySQL)](https://www.qoosky.io/techs/400f6b6f09)
- [例外発生時のレスポンスをアプリケーション共通で整形しよう。](http://spring-boot-reference.jp/%E4%BE%8B%E5%A4%96%E7%99%BA%E7%94%9F%E6%99%82%E3%81%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E5%85%B1%E9%80%9A%E3%81%AE%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9/)
- [Springでトランザクション管理](https://qiita.com/NagaokaKenichi/items/a279857cc2d22a35d0dd)