Spring Bootを利用したアプリケーションでDB接続を利用する際、@Transactionalアノテーションをつけたメソッド内でDB更新処理を実装するようにすると、そのメソッド単位で、DB更新処理が成功した場合にコミットし、失敗した場合にロールバックをすることができる。
今回は、Spring Bootアプリケーション内でMyBatisフレームワークを利用する状態で、Oracleに接続し@Transactionalアノテーションの挙動を調べてみたので、共有する。
前提条件
下記記事の実装が完了していること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加/変更したプログラムである。
USER_DATAテーブルのデータを格納するエンティティクラスの内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | package com.example.demo; import lombok.Data; /** * ユーザーデータテーブル(user_data)アクセス用エンティティ */ @Data public class UserData { /** ID */ private long id; /** 名前 */ private String name; /** 生年月日_年 */ private int birthY; /** 生年月日_月 */ private int birthM; /** 生年月日_日 */ private int birthD; /** 性別 */ private String sex; /** メモ */ private String memo; /** 性別(文字列) */ private String sex_value; } |
また、UserDataMapper.java、UserDataMapper.xmlの内容は以下の通りで、データ取得・データ更新を行うためのSQLを定義している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package com.example.demo; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserDataMapper { /** * 指定したIDをもつユーザーデータテーブル(user_data)のデータを取得する * @param id ID * @return ユーザーデータテーブル(user_data)の指定したIDのデータ */ UserData findById(Long id); /** * 指定したユーザーデータテーブル(user_data)のデータを更新する * @param userData ユーザーデータテーブル(user_data)の更新データ */ void update(UserData userData); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.UserDataMapper"> <resultMap id="userDataResultMap" type="com.example.demo.UserData" > <id column="id" property="id" jdbcType="BIGINT" /> <result column="name" property="name" jdbcType="VARCHAR" /> <result column="birthY" property="birthY" jdbcType="VARCHAR" /> <result column="birthM" property="birthM" jdbcType="VARCHAR" /> <result column="birthD" property="birthD" jdbcType="VARCHAR" /> <result column="sex" property="sex" jdbcType="VARCHAR" /> <result column="memo" property="memo" jdbcType="VARCHAR" /> <result column="sex_value" property="sex_value" jdbcType="VARCHAR" /> </resultMap> <select id="findById" parameterType="java.lang.Long" resultMap="userDataResultMap"> SELECT id , name , birth_year as birthY , birth_month as birthM , birth_day as birthD , sex , memo , CASE sex WHEN '1' THEN '男' WHEN '2' THEN '女' ELSE '' END AS sex_value FROM USER_DATA WHERE id = #{id} </select> <update id="update" parameterType="com.example.demo.UserData"> UPDATE USER_DATA SET name = #{name}, birth_year = #{birthY} , birth_month = #{birthM}, birth_day = #{birthD} , sex = #{sex}, memo = #{memo,jdbcType=VARCHAR} WHERE id = #{id} </update> </mapper> |
さらに、サービスクラスの内容は以下の通りで、transUserDataメソッド内でDB更新を2回実行していて、@Transactionalアノテーションを付与している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class DemoService { /** 2回目の更新後氏名を定義 */ /** USER_NAME_OK:13桁39バイトで更新OK、USER_NAME_NG:14桁42バイトで更新NG */ private static String USER_NAME_OK = "1234567890123"; private static String USER_NAME_NG = "12345678901234"; /** * ユーザーデータテーブル(user_data)へアクセスするマッパー */ @Autowired private UserDataMapper userDataMapper; /** * ユーザーデータテーブル(user_data)を更新するトランザクション */ // @Transactionalアノテーションで、このメソッド内のDB更新が全て成功すれば // コミットされ、そうでなければロールバックされる @Transactional public void transUserData() { UserData userData = userDataMapper.findById(Long.valueOf(1)); // ユーザーデータが取得できなければ処理を終了する if (userData == null) { System.out.println("id=1のユーザーデータは見つかりませんでした。"); return; } // 更新前データを表示 System.out.println("ユーザーデータ(更新前) : " + userData.toString()); // 氏名を更新する userData.setName("テスト プリン1 更新後"); userDataMapper.update(userData); // 更新後データを表示 userData = userDataMapper.findById(Long.valueOf(1)); System.out.println("ユーザーデータ(1回目更新後) : " + userData.toString()); // 氏名を再度更新する userData.setName(USER_NAME_OK); userDataMapper.update(userData); // 更新後データを表示 userData = userDataMapper.findById(Long.valueOf(1)); System.out.println("ユーザーデータ(2回目更新後) : " + userData.toString()); } } |
また、Spring Bootのメインクラスの内容は以下の通りで、サービスクラスのtransUserDataメソッドを呼び出している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication implements CommandLineRunner { /** * ユーザーデータテーブル(user_data)を更新する * トランザクションを含むサービス */ @Autowired private DemoService demoService; public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Override public void run(String... args) { try { // ユーザーデータテーブル(user_data)を更新する // トランザクションを呼び出す // DB更新に失敗するとExceptionがスローされる demoService.transUserData(); } catch (Exception ex) { System.err.println(ex); } } } |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-oracle-transactional/demo
サンプルプログラムの実行結果
サンプルプログラムの実行結果は以下の通りで、同一トランザクション内でDB更新が成功するとコミット、失敗するとロールバックされることが確認できる。
1) 以下のように、DemoServiceクラスの2回目の氏名更新時の設定値を、更新成功する値に設定する。
2) 1)の状態でSpring Bootのメインクラス(DemoApplication.java)を実行した結果、コンソールログに出力される内容は以下の通り。
3) 1)の状態で、実行前後でUSER_DATAテーブルの値を確認した結果は以下の通りで、コミットされ氏名が更新されることが確認できる。
<実行前>
1 | select * from user_data where id = 1 |
<実行後>
1 | select * from user_data where id = 1 |
4) 以下のように、DemoServiceクラスの2回目の氏名更新時の設定値を、更新失敗する値に設定する。
5) 4)の状態でSpring Bootのメインクラス(DemoApplication.java)を実行した結果、コンソールログに出力される内容は以下の通りで、DB更新エラーが発生していることが確認できる。
6) 4)の状態で、実行前後でUSER_DATAテーブルの値を確認した結果は以下の通りで、ロールバックされ氏名が更新されないことが確認できる。
<実行前>
1 | select * from user_data where id = 1 |
<実行後>
1 | select * from user_data where id = 1 |
要点まとめ
- Spring Bootを利用したアプリケーションでDB接続を利用する際、@Transactionalアノテーションをつけたメソッド内でDB更新処理を実装するようにすると、そのメソッド単位で、DB更新処理が成功した場合にコミットし、失敗した場合にロールバックをすることができる。