Spring+Domaでトランザクション分離レベルを設定する

### トランザクション分離レベルとは
データベース管理システム上でトランザクションが複数同時に行われた場合に、どれほどの一貫性、正確性で実行するかを4段階で定義したもの。

### 分離レベル
#### SERIALIZABLE（直列化可能）
最も独立性が高い分離レベル。
トランザクションを順番に実行（直列）するのと同じように他のトランザクションのデータ更新の影響を全く受けない。
アクセスが競合すると先のトランザクションの終了を待たなければならないため性能は低い。

#### REPEATABLE READ（読み取り対象のデータを常に読み取る） 
SERIALIZABLEに次いで独立性が高い分離レベル。
他のトランザクションによるデータ更新の影響を受けない。
トランザクション実行中は再び対象データを読み取っても同じ値が返ってくる。
しかし、他のトランザクションによるレコードの追加・削除の影響は受けるため、「ファントムリード」と呼ばれる現象が生じることがある

#### READ COMMITTED（確定した最新データを常に読み取る）
3番目に独立性が高い分離レベル。
他のトランザクションがコミットした変更の影響を受ける。
REPEATABLE READ同様、ファントムリードが生じることがある。
何度も同じデータを読み込むと他のトランザクションによる更新で値が変わってしまう「ノンリピータブルリード」が生じることがある。

#### READ UNCOMMITTED（確定していないデータまで読み取る）
最も独立性が低い分離レベル。
他のトランザクションが引き起こすあらゆる更新・変更の影響を受ける。
READ COMMITTED同様、ファントムリード、ノンリピータブルリードが生じることがある。
処理途中や不完全な状態のデータを読む込む「ダーティリード」が生じることがある。
処理を妨げるロックは最小限で済むので4つの分離レベルの中で最も高速に動作する。

### 今回やること
Spring + Doma2を使っているアプリケーションでトランザクション分離レベルを「READ UNCOMMITTED」にする

### 確認方法

```sql
SELECT @@transaction_isolation;
```

### やりかた①
１つのsqlファイルでSELECTの直前にSETコマンド実行

```sql:sample.sql
SET SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT
  @@transaction_isolation as iso
FROM
  user
WHERE
  id = /*userId*/-1
;
```

#### 結果

エラーとなり実行できない
```
{
  "error": {
    "message": "[DOMA2009] The SQL execution is failed.\nPATH=[xxxx/yyyy/zzz/samle.sql].\nSQL=[].\nThe cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.\nThe root cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.; SQL [SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;]; Statement.executeQuery() cannot issue statements that do not produce result sets.",
    "type": "internal_server_error"
  }
}
```

### やりかた②
DomaのSqlアノテーションでSETコマンドを実行してからSELECTする

```java:sample.java
@Sql("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED")
@Update
int setIso();
```

```sql:sample.sql
SELECT
  @@transaction_isolation as iso
FROM
  user
WHERE
  id = /*userId*/-1
;
```

#### 結果

①と同じくエラーとなり実行できない
```
{
  "error": {
    "message": "[DOMA2009] The SQL execution is failed.\nPATH=[xxxx/yyyy/zzz/samle.sql].\nSQL=[].\nThe cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.\nThe root cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.; SQL [SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;]; Statement.executeQuery() cannot issue statements that do not produce result sets.",
    "type": "internal_server_error"
  }
}
```

### やりかた③
別sqlファイルでSELECTの前にSETコマンド実行

```java:query.java
public Object getUser(@NonNull Integer userId) {
    int a = dao.setIso();
    return dao.selectUser(userId);
}
```

```java:dao.java
@Update(sqlFile = true)
int setIso();
```

```sql:setIso.sql
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
```

```sql:selectUser.sql
SELECT
  user.*,
  @@transaction_isolation as iso
FROM
  user
WHERE
  id = /*userId*/-1
;
```

#### 結果

①②と同じくエラーとなり実行できない
```
{
  "error": {
    "message": "[DOMA2009] The SQL execution is failed.\nPATH=[xxxx/yyyy/zzz/samle.sql].\nSQL=[].\nThe cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.\nThe root cause is as follows: java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result sets.; SQL [SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;]; Statement.executeQuery() cannot issue statements that do not produce result sets.",
    "type": "internal_server_error"
  }
}
```

### やりかた④
application.ymlで設定してみる

```yml:sample.yml
spring:
  datasource:
    hikari:
      transaction-isolation: TRANSACTION_READ_UNCOMMITTED
```

#### 結果
![スクリーンショット 2023-12-19 18.56.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439703/22cf083d-6fed-e309-a2b3-61d354db3493.png)
READ-UNCOMMITTEDになった

### やりかた⑤
Doma2のTransactionManagerを使ってみる

- [Doma2のTransactionManager](https://doma.readthedocs.io/en/latest/config.html?highlight=transaction#simple-definition)のsampleを参考にDbConfigクラスなどを用意して

```java:sample.java
TransactionManager tm = new DbConfig(URL, USERNAME, PASSWORD).getTransactionManager();
tm.requiresNew(TransactionIsolationLevel.READ_UNCOMMITTED, () -> {
    user.set(dao.selecrUser(userId));
});
```

#### 結果
![スクリーンショット 2023-12-19 18.28.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439703/73e68cce-f0b6-724d-f92f-63e49b822031.png)
変わっていなかった（やり方もうちょっと考えればできそう）


### やりかた⑥
アノテーションで設定

```sample.java
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
```

#### 結果
![スクリーンショット 2023-12-19 18.56.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439703/de7ad565-5069-72be-e27c-2e77384432dc.png)
READ-UNCOMMITTEDになった

### 注意
書き込み権限があるユーザで実施しないとSETコマンドが実行できません
※自分はこれでつまづいていました。。。

### まとめ
SpringのTransactionalアノテーションを使うのが一番楽そう。
[SpringのTransactionTemplete](https://www.baeldung.com/spring-programmatic-transaction-management#4-custom-transaction-configurations)も使えるかもしれないです（今回は試していません）