導(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í)行


可以看出,
當(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ù)回滾了。
代碼
后續(xù)還會(huì)整理一些Spring事務(wù),感覺自己還是學(xué)的不是特別精。
結(jié)語(yǔ)
換了工作之后一直用的是mybatis,還是很差異,為什么之前是沒遇到這種問題的。
之前用的是jpa。
用一個(gè)更新操作要寫@Modifing,外面一定要加上@Transactional注解,不然的話,不用等奇葩的異常出現(xiàn),自己自測(cè)的時(shí)候,就直接報(bào)錯(cuò)了