使い方を間違えると動作しない@Transactionalを動作させる使い方

Springアノテーション「```@Transactional```」はメソッドにつけるだけで自動的にトランザクションを行い、標準でRuntimeExceptionが発生した場合にロールバックを行ってくれる便利なものです。
しかし、```@Transactional```をつけただけではトランザクションを行われない場合があり、ビルドエラーにもならないので実際に動かしたらトランザクションしていなかったということがありました。
ということで、```@Transactional```を動作させるための使い方をまとめました。

# ```@Transactional```が動作するための条件

いろいろ動かして確認してみたところ、下記の条件を満たす必要があるようです。

- DIしているクラスのpublicメソッドであること
- DIしている別のクラスもしくはフレームワークから直接呼ばれていること

## DIしているクラスのpublicメソッド

クラスをDIする方法としてBean定義ファイルを使う方法もありますが、ここではSpringアノテーションを使用した実装を記載しておきます。

```java:ServiceClass.java
@Service
public class ServiceClass {

    @Transactional
	public void updateDB() {
    	// DB更新処理
	}
}
```
上記では```@Service```をクラスにつけておりますが、```@Controller```、```@Repository```などのDIを行う他のアノテーションでも動作します。

## DIしている別のクラスから直接呼ぶ

上記のServiceClassを呼び出すクラスが下記となります。

```java:ControllerClass.java
@RequestMapping("/sample/*")
@Controller
public class ControllerClass {

	@Autowired
	ServiceClass serviceClass;

    @RequestMapping(value = { "index" })
    public String index(){
    	serviceClass.updateDB();

        return "sample/index";
    }
}
```
こちらも```@Controller```を使用してDIしているクラスです。
```@Autowired```を使用してServiceClassのインスタンスを自動で作成し、updateDBメソッドを呼び出すことでトランザクションが動作します。

## フレームワークから直接呼ぶ
Springのフレームワークから```@Transactional```をつけているメソッドが呼び出された場合も動作します。
下記の場合は、```@RequestMapping```の設定に該当するリクエストが来てメソッドが呼び出されることでトランザクションが動作します。

```java:ControllerClass.java
@RequestMapping("/sample/*")
@Controller
public class ControllerClass {

    @Autowired
    ServiceClass serviceClass;

    @RequestMapping(value = { "index" })
    @Transactional
    public String index(){
        serviceClass.updateDB();

        return "sample/index";
    }
}
```

```java:ServiceClass.java
@Service
public class ServiceClass {

    public void updateDB() {
        // DB更新処理
    }
}
```

また、```@Scheduled```による時間指定でのメソッド呼び出しでも同様にトランザクションが動作します。

```java:ControllerClass.java
@Controller
public class ControllerClass {

    @Autowired
    ServiceClass serviceClass;

    @Scheduled(cron = "0 0 10 * * *")
    @Transactional
    public String index(){
        serviceClass.updateDB();

        return "sample/index";
    }
}
```

# 実装例

実用的な実装を考えると、RuntimeException以外の例外が発生した場合もロールバックしたいので
```@Transactional(rollbackFor = Exception.class)```としてExceptionおよびExceptionを継承しているクラスがthrowされるとロールバックされるように設定します。
呼び出し元のメソッドでtry-catchして成功、失敗で処理を分けます。

```java:ControllerClass.java
@RequestMapping("/sample/*")
@Controller
public class ControllerClass {

    @Autowired
    ServiceClass serviceClass;

    @RequestMapping(value = { "exec" })
    public String exec(Model model){

        try {
            // トランザクションを行うメソッドの呼び出し
            serviceClass.transaction();

            // 成功した場合の処理
            model.addAttribute("message", "処理に成功しました。");
        } catch (Exception e) {
            // 処理に失敗した場合の処理
            if (e.getMessage() != null) {
                 model.addAttribute("message", e.getMessage());
            } else {
                 model.addAttribute("message", "エラーが発生しました。");
            }
            return "sample/index";
        }
        return "sample/complete";
    }
}
```

```java:ServiceClass.java
@Service
public class ServiceClass {

    @Transactional(rollbackFor = Exception.class)
    public void transaction() throws Exception {
        // トランザクションしたい一連の処理
        // ロールバックしたい場合は下記のようにExceptionをthrowする
        throw new Exception("処理に失敗しました。");
    }
}
```