Springアノテーション「@Transactional
」はメソッドにつけるだけで自動的にトランザクションを行い、標準でRuntimeExceptionが発生した場合にロールバックを行ってくれる便利なものです。
しかし、@Transactional
をつけただけではトランザクションを行われない場合があり、ビルドエラーにもならないので実際に動かしたらトランザクションしていなかったということがありました。
ということで、@Transactional
を動作させるための使い方をまとめました。
@Transactional
が動作するための条件
いろいろ動かして確認してみたところ、下記の条件を満たす必要があるようです。
- DIしているクラスのpublicメソッドであること
- DIしている別のクラスもしくはフレームワークから直接呼ばれていること
DIしているクラスのpublicメソッド
クラスをDIする方法としてBean定義ファイルを使う方法もありますが、ここではSpringアノテーションを使用した実装を記載しておきます。
@Service
public class ServiceClass {
@Transactional
public void updateDB() {
// DB更新処理
}
}
上記では@Service
をクラスにつけておりますが、@Controller
、@Repository
などのDIを行う他のアノテーションでも動作します。
DIしている別のクラスから直接呼ぶ
上記のServiceClassを呼び出すクラスが下記となります。
@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
の設定に該当するリクエストが来てメソッドが呼び出されることでトランザクションが動作します。
@RequestMapping("/sample/*")
@Controller
public class ControllerClass {
@Autowired
ServiceClass serviceClass;
@RequestMapping(value = { "index" })
@Transactional
public String index(){
serviceClass.updateDB();
return "sample/index";
}
}
@Service
public class ServiceClass {
public void updateDB() {
// DB更新処理
}
}
また、@Scheduled
による時間指定でのメソッド呼び出しでも同様にトランザクションが動作します。
@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して成功、失敗で処理を分けます。
@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";
}
}
@Service
public class ServiceClass {
@Transactional(rollbackFor = Exception.class)
public void transaction() throws Exception {
// トランザクションしたい一連の処理
// ロールバックしたい場合は下記のようにExceptionをthrowする
throw new Exception("処理に失敗しました。");
}
}