モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

Spring Boot におけるトランザクション処理 (MyBatis/MySQL)

モーダルを閉じる

ステッカーを選択してください

お支払い手続きへ
モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2017/07/03
最終更新最終更新
2021/09/07
記事区分記事区分
一般公開

目次

    Spring Bootでの実用的な機能をわかりやすく解説中

    Spring フレームワークにおける @Transactional アノテーションを利用すると DB トランザクション処理が簡単に設定できます。ここでは特に Spring Boot から MyBatis を経由して MySQL を利用する場合を対象としますが、JDBC を利用して他の DB を操作する場合も考え方は同じです。

    参考ドキュメント

    サンプルプロジェクト

    .
    |-- build.gradle
    |-- gradle
    |   `-- wrapper
    |       |-- gradle-wrapper.jar
    |       `-- gradle-wrapper.properties
    |-- gradlew
    |-- gradlew.bat
    `-- src
        `-- main
            |-- java
            |   `-- hello
            |       |-- Application.java
            |       |-- HelloController.java
            |       `-- HelloMapper.java
            `-- resources
                |-- application.yml
                |-- data.sql
                `-- schema.sql
    

    build.gradle

    buildscript {
        ext {
            springBootVersion = '1.5.3.RELEASE'
        }
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        }
    }
    
    apply plugin: 'java'
    apply plugin: 'eclipse'
    apply plugin: 'idea'
    apply plugin: 'org.springframework.boot'
    
    jar {
        baseName = 'gs-spring-boot'
        version =  '0.1.0'
    }
    
    repositories {
        mavenCentral()
    }
    
    sourceCompatibility = 1.8
    targetCompatibility = 1.8
    
    dependencies {
        compile('org.springframework.boot:spring-boot-starter-web')
        compile('mysql:mysql-connector-java:6.0.6')
        compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.0')
    }
    

    src/main/resources/application.yml

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/mydb
        username: myuser
        password: myuser
        driver-class-name: com.mysql.jdbc.Driver
    

    src/main/resources/schema.sql

    DROP TABLE IF EXISTS city;
    CREATE TABLE city (
        id INT PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(255),
        state VARCHAR(255),
        country VARCHAR(255)
    );
    

    src/main/resources/data.sql

    INSERT INTO city (id, name, state, country) VALUES (1, 'San Francisco1', 'CA1', 'US1');
    INSERT INTO city (id, name, state, country) VALUES (2, 'San Francisco2', 'CA2', 'US2');
    

    src/main/java/hello/Application.java

    package hello;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    

    src/main/java/hello/HelloMapper.java

    package hello;
    
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    
    @Mapper
    public interface HelloMapper {
        @Select("UPDATE city SET id = #{idAfter} WHERE id = #{idBefore}")
        void updateCityId(@Param("idBefore") int idBefore, @Param("idAfter") int idAfter);
    }
    

    src/main/java/hello/HelloController.java

    本ページの目的となるトランザクション設定がなされる箇所です。

    package hello;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @Autowired
        private HelloMapper helloMapper;
    
        @Transactional
        @RequestMapping("/")
        public String index() {
            helloMapper.updateCityId(1, 9991);
            helloMapper.updateCityId(2, 9992);
            throw new RuntimeException(); // 実行時例外を発生させます。
        }
    }
    

    @Transactional が設定してあるため http://localhost:8080/ にアクセスした後も

    mysql> select * from city;
    +------+----------------+-------+---------+
    | id   | name           | state | country |
    +------+----------------+-------+---------+
    | 9991 | San Francisco1 | CA1   | US1     |
    | 9992 | San Francisco2 | CA2   | US2     |
    +------+----------------+-------+---------+
    2 rows in set (0.00 sec)
    

    とはならず、ロールバックされて以下のような初期状態のままになります。

    mysql> select * from city;
    +----+----------------+-------+---------+
    | id | name           | state | country |
    +----+----------------+-------+---------+
    |  1 | San Francisco1 | CA1   | US1     |
    |  2 | San Francisco2 | CA2   | US2     |
    +----+----------------+-------+---------+
    2 rows in set (0.00 sec)
    

    メソッド全体の処理が完了するまでコミットされず、途中で非検査例外が発生すると、すべての処理が巻き戻ることが確認できました。

    meaning that any failure causes the entire operation to roll back to its previous state, and to re-throw the original exception. This means that none of the people will be added to BOOKINGS if one person fails to be added.
    https://spring.io/guides/gs/managing-transactions/

    トランザクション設定の補足

    同一クラス内の別メソッドでは機能しない

    同一クラス内の別メソッドに @Transactional を設定したとしても、有効に機能しません。ただし、その場合でもコンパイルエラーにはなりません。@Service や @Repository を設定した Bean クラスを別途用意する必要があります。

    This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.
    https://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html

    以下のサンプルにおいて、簡単のためインナークラスに @Service を付与しています。

    package hello;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @Autowired
        private HelloMapper helloMapper;
    
        @Autowired
        private HelloService helloService;
    
        @RequestMapping("/")
        public String index() {
            helloService.updateCities(); // ロールバックされる
            // this.updateCities(); // ロールバックされない
            return "hello";
        }
    
        // 以下の設定は無効です。
        @Transactional
        public void updateCities() {
            helloMapper.updateCityId(1, 9991);
            helloMapper.updateCityId(2, 9992);
            throw new RuntimeException();
        }
    
        // 別クラス (ここでは簡単のためインナークラス)
        @Service
        public class HelloService {
    
            // 以下の設定は有効です。
            @Transactional
            public void updateCities() {
                helloMapper.updateCityId(1, 9991);
                helloMapper.updateCityId(2, 9992);
                throw new RuntimeException();
            }
        }
    }
    

    クラス全体に設定可能

    クラスに @Transactional アノテーションを設定できます。個別に設定を上書きたい場合はメソッドに別途設定します。

    package hello;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @Autowired
        private HelloMapper helloMapper;
    
        @Autowired
        private HelloService helloService;
    
        @RequestMapping("/")
        public String index() {
            helloService.updateCities();
            return "hello";
        }
    
        @Service
        @Transactional
        public class HelloService {
    
            public void updateCities() {
                helloMapper.updateCityId(1, 9991);
                helloMapper.updateCityId(2, 9992);
                throw new RuntimeException();
            }
        }
    }
    

    public 以外のメソッドに設定しても機能しない

    @Transactional アノテーションを public 以外のメソッドに設定した場合、コンパイルエラーにはなりませんが、有効に機能しません。

    When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings.
    https://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html

    package hello;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @Autowired
        private HelloMapper helloMapper;
    
        @Autowired
        private HelloService helloService;
    
        @RequestMapping("/")
        public String index() {
            helloService.updateCities();
            return "hello";
        }
    
        @Service
        public class HelloService {
    
            @Transactional
            private void updateCities() { // ロールバックされない
            // public void updateCities() { // ロールバックされる
                helloMapper.updateCityId(1, 9991);
                helloMapper.updateCityId(2, 9992);
                throw new RuntimeException();
            }
        }
    }
    

    インターフェースに設定することは非推奨

    @Transactional アノテーションをインターフェース等に設定した場合、コンパイルエラーにはなりませんが、非推奨であり、有効に機能しない可能性があります。

    Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the @Transactional annotation, as opposed to annotating interfaces.
    https://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html

    package hello;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @Autowired
        private HelloMapper helloMapper;
    
        @Autowired
        private HelloService helloService;
    
        @RequestMapping("/")
        public String index() {
            helloService.updateCities();
            return "hello";
        }
    
        @Transactional  // ロールバックされない
        public interface IHelloService {
            void updateCities();
        }
    
        @Service
        public class HelloService implements IHelloService {
    
            public void updateCities() {
                helloMapper.updateCityId(1, 9991);
                helloMapper.updateCityId(2, 9992);
                throw new RuntimeException();
            }
        }
    }
    

    検査例外はロールバックされない

    @Transactional 既定の設定では、実行時例外のみロールバックされます。

    Any RuntimeException triggers rollback, and any checked Exception does not.
    https://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html#transaction-declarative-attransactional-settings

    検査例外をロールバックの対象に含めるためには、以下のいずれかを設定します。

    @Transactional(rollbackFor = Exception.class)
    @Transactional(rollbackForClassName = "Exception")
    

    MyExceptionException を直接継承しており、検査例外です。

    package hello;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @Autowired
        private HelloMapper helloMapper;
    
        @Autowired
        private HelloService helloService;
    
        @RequestMapping("/")
        public String index() {
            try {
                helloService.updateCities();
            }
            catch (MyException e) {
                e.printStackTrace();
            }
            return "hello";
        }
    
        public class MyException extends Exception {
            private static final long serialVersionUID = 1L;
            public MyException(Exception e) {}
        }
    
        @Service
        public class HelloService {
    
            @Transactional // ロールバックされない
            // @Transactional(rollbackFor = Exception.class) // ロールバックされる
            // @Transactional(rollbackForClassName = "Exception") // ロールバックされる
            public void updateCities() throws MyException {
                try {
                    helloMapper.updateCityId(1, 9991);
                    helloMapper.updateCityId(2, 9992);
                    throw new Exception();
                }
                catch(Exception e) {
                    throw new MyException(e);
                }
            }
        }
    }
    

    読取専用のトランザクションを設定

    @Transactional(readOnly = true) を設定することで、更新系のクエリが発行された際に例外が発生するようになります。

    java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
    

    以下のサンプルにおける UPDATE クエリは失敗します。

    package hello;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @Autowired
        private HelloMapper helloMapper;
    
        @Autowired
        private HelloService helloService;
    
        @RequestMapping("/")
        public String index() {
            helloService.updateCities();
            return "hello";
        }
    
        @Service
        public class HelloService {
    
            @Transactional(readOnly = true)
            public void updateCities() {
                helloMapper.updateCityId(1, 9991);
                helloMapper.updateCityId(2, 9992);
            }
        }
    }
    
    Likeボタン(off)0
    詳細設定を開く/閉じる
    アカウント プロフィール画像

    Spring Bootでの実用的な機能をわかりやすく解説中

    記事の執筆者にステッカーを贈る

    有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

    >>さらに詳しくステッカーを贈る
    ステッカーを贈る コンセプト画像

    Feedbacks

    Feedbacks コンセプト画像

      ログインするとコメントを投稿できます。

      ログインする

      関連記事

      • Spring Security フォームログインのサンプルコード
        Spring フレームワークによる Web アプリケーション開発で、ログイン処理を実装する際は Spring Security が便利です。ここでは特に Spring Boot で Web アプリケーションを開発する場合を対象とし、フォームによる ID/Password ログインを行うためのサンプルコードをまとめます。 公式ドキュメント [Spring Security チュートリアル](http...
        えびちゃんえびちゃん11/4/2019に更新
        いいねアイコン画像0
      • Java配列の宣言方法 (C/C++との違い)
        Javaの配列 Javaの配列宣言方法はC/C++と似ているようで若干異なる。 初期化しない場合 C/C++の int array[10]; はJavaでは int array[] = new int[10]; となる。同様にC/C++の int array[3][3]; はJavaでは int array[][] = new int[3][3]; となる。 初期化
        てんとうむしてんとうむし4/13/2018に更新
        いいねアイコン画像0
      • PlantUML による UML 図の描き方
        サムネイル画像-c788fffde5
        PlantUML はテキスト形式で表現されたシーケンス図やクラス図といった UML (Unified Modeling Language) 図の情報から画像を生成するためのツールです。簡単な使い方をまとめます。 インストール方法の選択 Atom や Eclipse のプラグインをインストールしてエディタから利用する方法、JAR をダウンロードして Java コマンドで実行する方法、Redmine ...
        kentakenta12/21/2019に更新
        いいねアイコン画像0
      • Akka HTTP サンプルコード (Scala)
        サムネイル画像-a98142497c
        Akka アクターを用いて実装された汎用 HTTP フレームワークです。Spray の後継です。コアモジュールである akka-http-core は 2016/2/17 に experimental が外れました。akka-http などのいくつかのサブモジュールは 2016/3/1 現在 experimental のままですが、基本的な
        雄太雄太9/7/2021に更新
        いいねアイコン画像0
      • Kestrel の使用例
        Kestrel は Message Queue (MQ) の実装のひとつです。一般に MQ はアプリケーション間やプロセス間、スレッド間で非同期に通信するために用いられます。メッセージの送信側は MQ に書き込めば受信側の応答を待たずに次の処理に非同期に進むことができます。Kestrel はわずか 2500 行程の Scala で実装されており JVM で動作します。MQ 自体はメモリ上に存在する...
        したくんしたくん9/12/2017に更新
        いいねアイコン画像0