Spring Bootでのトランザクション設定方法
Spring Bootでトランザクションを設定する方法としては、TransactionManagerを使う方法と@Transactionalというアノテーションを使う方法があります。
コード量の関係やメンテナンス性などを考慮しても後者を選んで設定している人が多いのではないでしょうか。
ただ、設定したはいいものの上手く効かないという人もいると思います。
今回は後者を選んで設定した場合で、トランザクションが上手くいかない場合のチェック項目を記します。
以下に当てはまる場合はトランザクションが効かないので注意が必要です。
メソッドがpublicになっているか
@Transactionalはpublicのメソッドやクラスにしか効きません。(理由は後述)
メソッドに設定した場合に関してですが、Spring Bootではにpublicやprivateなどの修飾子をつけない場合、デフォルトではpublicの扱いになります。
なので、publicをつけなくてもpublicの扱いになるから大丈夫だろうと思われる人もいるかもしれませんが、ちゃんとpublicをつけないとトランザクションが効かないのです。
ダメな例)
@Service public class TestService { @Transactional void testMethod() { // 更新処理 } }
正しい例)
@Service public class TestService { @Transactional public void testMethod() { // 更新処理 } }
@RequestMappingをつけているコントローラ自体にアノテーションを付与していないか
Spring Bootではパスによってコントローラのメソッドを呼び出す場合、@RequestMappingが付与されますが、そのようなメソッド自体に@Transactionalを付与してもトランザクションは効きません。
@AutowiredでDIしたクラスのメソッドを直接呼び出す形でないといけません。
ダメな例)
@Controller public class TestController { @RequestMapping(value = "test", method = RequestMethod.POST) @Transactional ResponseEntity test() { // 更新処理 return "ok"; } }
正しい例)
@Controller public class TestController { @Autowired TestService testService; @RequestMapping(value = "test", method = RequestMethod.POST) ResponseEntity test() { try { testService.testMethod(); // このメソッド内で更新処理がされる } catch (Exception e) { e.printStackTrace(); } return "ok"; } }
@Service public class TestService { @Transactional public void testMethod() { // 更新処理 } }
try~catchで囲まれているか
上記の正しい例にある通り、トランザクションを使用した更新処理はtry~catchで囲む必要があります。
トランザクションでは、エラーが発生した場合に更新されたデータが全て元に戻る(ロールバック)のですが、この時にExceptionの例外でないとロールバックはされないのです。
ダメな例)
@Controller public class TestController { @Autowired TestService testService; @RequestMapping(value = "test", method = RequestMethod.POST) ResponseEntity test() { testService.testMethod(); // このメソッド内で更新処理がされる return "ok"; } }
@Service public class TestService { @Transactional public void testMethod() { // 更新処理 } }
正しい例)
@Controller public class TestController { @Autowired TestService testService; @RequestMapping(value = "test", method = RequestMethod.POST) ResponseEntity test() { try { testService.testMethod(); // このメソッド内で更新処理がされる } catch (Exception e) { e.printStackTrace(); } return "ok"; } }
@Service public class TestService { @Transactional public void testMethod() { // 更新処理 } }
更新処理をするメソッドが直接呼ばれているか
これはクラス全体ではなくメソッドにアノテーションを付与した場合に関係することですが、コントローラなどから別メソッドを呼び出して更新処理をする場合、@AutowiredでDIしたクラスのメソッドを直接呼び出す形でないとトランザクションは効きません。
例えば以下の例では上手くいきません。
ダメな例)
@Controller public class TestController { @Autowired TestService testService; @RequestMapping(value = "test", method = RequestMethod.POST) ResponseEntity test() { try { testService.testMethod(); // このメソッド内で更新処理がされる } catch (Exception e) { e.printStackTrace(); } return "ok"; } }
@Service public class TestService { public void testMethod() { testInsertMethod() } @Transactional public void testInsertMethod() { // 更新処理 } }
上記の例では、一度testMethod()というメソッドを経由してから更新処理を行うメソッドtestInsertMethod()を実行していますよね。
これではDIしたクラスのメソッドを直接呼び出しているわけではないので、トランザクションはかかりません。
以下のように直接呼び出しましょう。
正しい例)
@Controller public class TestController { @Autowired TestService testService; @RequestMapping(value = "test", method = RequestMethod.POST) ResponseEntity test() { try { testService.testMethod(); // このメソッド内で更新処理がされる } catch (Exception e) { e.printStackTrace(); } return "ok"; } }
@Service public class TestService { @Transactional public void testMethod() { // 更新処理 } }
先ほど@Transactionalはpublicのメソッドやクラスにしか効きないと記載しましたが、メソッドではコントローラなどから直接呼び出す必要がある以上、いずれにせよprivateにはできないのです。
継承されたメソッドの場合は子クラスに@Transactionalを付与
トランザクションを設定したいメソッドが親クラスの抽象メソッドなどを継承して実装するパターンの場合、抽象メソッドに@Transactionalが付いていても子クラスで実装するメソッドにも@Transactionalを付与しないとトランザクションは効きません。
通常、継承されたメソッドはアノテーションも継承されるものです。
しかしトランザクションの場合は、親メソッドにアノテーションを付与するだけではやはり直接呼び出していないと見なされ、トランザクションは上手く効きません。
今回の例はServiceクラスのみ書きます。
ダメな例)
@Service abstract public class TestBaseService { @Transactional abstract public void testMethod() } @Service public class TestService extends TestBaseService { @Override public void testMethod() { // 更新処理 } }
上記の例では、testMethodは抽象メソッドの実装になりますが、testMethod()自身にも@Transactionalを付与していないのでトランザクションは効きません。
正しい例)
@Service abstract public class TestBaseService { @Transactional abstract public void testMethod() } @Service public class TestService extends TestBaseService { @Override @Transactional public void testMethod() { // 更新処理 } }
上記のように実装メソッドにも@Transactionalを付与する必要があることに注意しましょう。
まとめ
ここまでトランザションが効かない場合の主なチェック項目を挙げましたが、上記を満たせばほとんどの場合トランザクションが効くようになるはずです。
- 作者:掌田津耶乃
- 発売日: 2018/01/30
- メディア: 単行本
はじめてのSpring Boot―スプリング・フレームワークで簡単Javaアプリ開発 (I・O BOOKS)
- 作者:俊明, 槙
- 発売日: 2016/09/01
- メディア: 単行本
Spring Bootビギナーズガイド: Webアプリケーション開発を高速化せよ! PRIMERシリーズ (libroブックス)
- 作者:掌田津耶乃
- 発売日: 2015/05/28
- メディア: Kindle版