事象
SpringBootで@Transactionalの動きを見たかったので、下記のようなサービスクラスと適当なコントローラ、リポジトリを用意して該当のエンドポイントにアクセスしました。
期待結果としてはスローしているので、ロールバックされると思っていました。
package com.example.demo.service;
import com.example.demo.mapper.TestMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper testMapper;
@Override
public void registerError(String id) throws Exception {
testMapper.register(id);
throw new Exception("ロールバック確認");
}
}
package com.example.demo.api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.service.TestService;
@RestController
public class TestApi {
@Autowired
private TestService testService;
@PostMapping("error")
public void error() throws Exception {
testService.registerError("1000");
}
}
結果は下記エラーがでていました。
java.lang.Exception: ロールバック確認
oracle.jdbc.OracleDatabaseException: ORA-00001: 一意制約(PDBADMIN.PK_PS)に反しています
ロールバック確認のほうは期待通りですが、一意制約違反は予想外。
データが残ってたかもと思い、データ削除して再実行しても同じ。
他にも下記内容を確認しましたが異常なし
- コントローラが複数回サービスを呼んでないか
- サービスが複数回登録処理を呼んでないか
- リポジトリ(Mybatisのxmlを使用)で変な記述してないか
原因
今回throwしているExceptionは@Transactionalの管理対象外だから。
下記であればロールバックされているのを確認できます。
throw new RuntimeException("ロールバック確認");
Exceptionでロールバックしたい場合は、@Transactionalを下記に変更すると
同様にロールバックしているのを確認できました。
@Transactional(rollbackFor = Exception.class)
おわり
ロールバックが効かなかったとして、2回目の登録をしようとしていたのは謎のままです。
throwを外した場合は正常に1件のみ登録されていました。