Spring @Transactional 之你的事務(wù)生效了嗎?

導(dǎo)言

你以為你加了事務(wù)注解就能生效嗎?

代碼片段

@Service
public class ScoreService {
  @Autowired
  private ScoreMapper scoreMapper;
  public void testTransactional(Score scoreOne,Score scoreTwo,Score scoreThree) {
    // DO SOMETHING
    updateScores(scoreOne,scoreTwo,scoreThree);
    // DO SOMETHING
  }
  @Transactional
  public void updateScores(Score scoreOne,Score scoreTwo,Score scoreThree){
    updateScoreOne(scoreOne);
    updateScoreTwo(scoreTwo);
    updateScoreThree(scoreThree);
  }
  private void updateScoreOne(Score one) {
    updateScore(one);
  }
  private void updateScoreTwo(Score two) {
    updateScore(two);
  }
  private void updateScoreThree(Score three) {
    updateScore(three);
  }
  private void updateScore(Score score) {
    if (score.getScore() == -1) {
      throw new RuntimeException();
    }
    scoreMapper.updateById(score);
  }
  @Transactional
  public void testTransactionalTwo(Score scoreOne,Score scoreTwo,Score scoreThree) {
    updateScores(scoreOne,scoreTwo,scoreThree);
  }
}

在工作的時(shí)候,用的是spring boot + mybatis,見到類似上面的代碼

就是在做一些操作之后,把保存方法都放到同一個(gè)類里面的方法里面,然后加個(gè)事務(wù)注解。要么全部成功要么全部失敗。這么做是為了把事務(wù)做小。

其實(shí)一開始見到這種寫法的時(shí)候,是感覺有點(diǎn)怪的。但是又說(shuō)不出哪里怪。

最近sonar新出的規(guī)則又掃出了這種bug,一開始還不以為然。

直到在做codeReview的時(shí)候,看到自己之前寫的異步調(diào)用
是使用Spring框架的@Async實(shí)現(xiàn)的

剛開始用,遇到一個(gè)很神奇的事情
就是調(diào)用方法跟異步方法放在同一個(gè)類里面,異步方法變成同步了。

后來(lái)查閱資料才知道
Spring @Async是通過切面來(lái)完成的,如果是同個(gè)類,走的是本地方法,不會(huì)通過切面。

然后突然間就想到了這個(gè)問題了。查了資料,果然Spring 的 @Transactional也是通過切面來(lái)做的,那么上面的寫法就有問題了。

測(cè)試代碼
當(dāng)score.score == -1 的時(shí)候,拋出一個(gè)錯(cuò)誤,測(cè)試回滾

public class CommonTest {
  @Autowired
  private ScoreService scoreService;
  @Autowired
  private ScoreMapper scoreMapper;
  @Test
  public void testSave(){
    Score score1 = new Score();
    Score score2 = new Score();
    Score score3 = new Score();
    score1.setId(1L);
    score2.setId(2L);
    score3.setId(3L);
    score1.setScore(11L);
    score2.setScore(-1L);
    score3.setScore(33L);

    List<Score> befores = scoreMapper.selectBatchIds(Lists.newArrayList(1,2,3));
    befores.forEach(System.out::println);
    try {
      scoreService.testTransactional(score1,score2,score3);
    } catch (Exception e) {
      System.out.println("出現(xiàn)了一個(gè)錯(cuò)誤");
    }
    List<Score> afters = scoreMapper.selectBatchIds(Lists.newArrayList(1,2,3));
    afters.forEach(System.out::println);
  }

  @Test
  public void testSaveTwo(){
    Score score1 = new Score();
    Score score2 = new Score();
    Score score3 = new Score();
    score1.setId(1L);
    score2.setId(2L);
    score3.setId(3L);
    score1.setScore(11L);
    score2.setScore(-1L);
    score3.setScore(33L);

    List<Score> befores = scoreMapper.selectBatchIds(Lists.newArrayList(1,2,3));
    befores.forEach(System.out::println);
    try {
      scoreService.testTransactionalTwo(score1,score2,score3);
    } catch (Exception e) {
      System.out.println("出現(xiàn)了一個(gè)錯(cuò)誤");
    }
    List<Score> afters = scoreMapper.selectBatchIds(Lists.newArrayList(1,2,3));
    afters.forEach(System.out::println);
  }
}

執(zhí)行


testSave()執(zhí)行結(jié)果
testSaveTwo()執(zhí)行結(jié)果

可以看出,
當(dāng)執(zhí)行testSave的時(shí)候,scoreOne成功保存了,所以說(shuō),事務(wù)沒有回滾。也可以說(shuō)Spring沒有代理這個(gè)事務(wù)。
當(dāng)執(zhí)行testSaveTwo的時(shí)候,saveScoreOne已經(jīng)執(zhí)行了,當(dāng)要保存saveScoreTwo的時(shí)候拋出了異常,整個(gè)事務(wù)回滾了。

代碼

https://gitee.com/allen_learn_test/transaction-test.git

后續(xù)還會(huì)整理一些Spring事務(wù),感覺自己還是學(xué)的不是特別精。

結(jié)語(yǔ)

換了工作之后一直用的是mybatis,還是很差異,為什么之前是沒遇到這種問題的。

之前用的是jpa。

用一個(gè)更新操作要寫@Modifing,外面一定要加上@Transactional注解,不然的話,不用等奇葩的異常出現(xiàn),自己自測(cè)的時(shí)候,就直接報(bào)錯(cuò)了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容