😊

繰り返し処理+try構文内では@Transactionalでロールバックできない

2023/05/25に公開

概要

springbootでは@Transactionalでトランザクション管理するが、特定の条件ではロールバック出来ない

環境

  • springboot2.7.5
  • kotlin1.6.21

下記の様にサービスクラスのloop関数で繰り返し処理を行い、その繰り返し処理内でtrycatch文で発生したExceptionをcatchする場合、Exceptionが投げられてもinsert関数で作成したレコードがロールバックされず不正なデータが残ってしまう。原因はおそらくSampleUserServiceに設定した@TransactionalがExceptionが投げられた事を判断する前にcatch文でExceptionを握りつぶしてしまっているため

コントローラクラス

@RestController
@RequestMapping("/api/sample")
class SampleApiController(
  val sampleUserService: SampleUserService
) {
  @GetMapping("/test")
  fun test() {
    sampleUserService.loop()
  }

サービスクラス

@Service
@Transactional(rollbackForClassName = ["Exception"])
class SampleUserService(
  private val sampleUserRepository: SampleUserRepository
) {

  fun loop() {
    val list = listOf("1", "2", "3")
    list.forEach {
      try {
        //try構文内でinsert関数を実行
        insert(it)
      } catch (e: Exception) {
        println("キャッチ")
      }
    }
  }

   fun insert(str: String) {
    val newUser = User(
      userId = str
    )
    //レコードの挿入
     sampleUserRepository.insert(newUser)
     //Exceptionを投げる
    throw Exception("")
  }
 }

解決方法

insert関数を別のサービスクラスSampleUserService2に定義してloop関数内で呼び出すとロールバックが成功する。SampleUserService2内で定義された@Transactionalではexceptionがキャッチできるため

コントローラクラス

@RestController
@RequestMapping("/api/sample")
class SampleApiController(
  val sampleUserService: SampleUserService
) {
  @GetMapping("/test")
  fun test() {
    sampleUserService.loop()
  }

サービスクラス1

@Service
@Transactional(rollbackForClassName = ["Exception"])
class SampleUserService(
  private val sampleUserService2: SampleUserService2
) {

  fun loop() {
    val list = listOf("1", "2", "3")
    list.forEach {
      try {
        //try構文内で別サービスに定義したinsert関数を実行
        sampleUserService2.insert(it)
      } catch (e: Exception) {
        println("キャッチ")
      }
    }
  }
 }

サービスクラス2

@Service
//トランザクションは新規に作成する様にオプションを追加
@Transactional(propagation = Propagation.REQUIRES_NEW)
class SampleUserService2(
  private val sampleUserRepository: SampleUserRepository
) {

 
   fun insert(str: String) {
    val newUser = User(
      userId = str
    )
    //レコードの挿入
     sampleUserRepository.insert(newUser)
     //Exceptionを投げる
    throw Exception("")
  }
 }

Discussion