前言
SpringBoot下想要使用事務(wù)非常簡單,只需要在Service的類或方法上面加上一個@Transactional注解即可實(shí)現(xiàn)失敗自動回滾。大部分情況下,默認(rèn)的@Transactional就能很好的滿足需求了,但是更加深入的了解@Transactional還是很有必要的。
傳播行為
在@Transactional注解中,可以propagation屬性用來配置事務(wù)傳播,支持7種不同的傳播機(jī)制
REQUIRED:業(yè)務(wù)方法需要在一個事務(wù)中運(yùn)行,如果方法運(yùn)行時,已處在一個事務(wù)中,那么就加入該事務(wù),否則自己創(chuàng)建一個新的事務(wù)。這是spring默 認(rèn)的傳播行為。
NOT_SUPPORTED:聲明方法不需要事務(wù)。如果方法沒有關(guān)聯(lián)到一個事務(wù),容器不會為他開啟事務(wù),如果方法在一個事務(wù)中被調(diào)用,該事務(wù)會被掛起,調(diào)用結(jié)束后,原先的事務(wù)會恢復(fù)執(zhí)行。
REQUIRES_NEW:不管是否存在事務(wù),該方法總會為自己發(fā)起一個新的事務(wù)。如果方法已經(jīng)運(yùn)行在一個事務(wù)中,則原有事務(wù)掛起,新的事務(wù)被創(chuàng)建。
MANDATORY:該方法只能在一個已經(jīng)存在的事務(wù)中執(zhí)行,業(yè)務(wù)方法不能發(fā)起自己的事務(wù)。如果在沒有事務(wù)的環(huán)境下被調(diào)用,容器拋出例外。
SUPPORTS:該方法在某個事務(wù)范圍內(nèi)被調(diào)用,則方法成為該事務(wù)的一部分。如果方法在該事務(wù)范圍外被調(diào)用,該方法就在沒有事務(wù)的環(huán)境下執(zhí)行。
NEVER:該方法絕對不能在事務(wù)范圍內(nèi)執(zhí)行。如果在就拋異常。只有該方法沒有關(guān)聯(lián)到任何事務(wù),才正常執(zhí)行。
NESTED:如果一個活動的事務(wù)存在,則運(yùn)行在一個嵌套的事務(wù)中。如果沒有活動事務(wù),則按REQUIRED屬性執(zhí)行。它使用了一個單獨(dú)的事務(wù),這個事務(wù)擁有多個可以回滾的保存點(diǎn)。內(nèi)部事務(wù)的回滾不會對外部事務(wù)造成影響。它只對DataSourceTransactionManager事務(wù)管理器起效。
實(shí)驗(yàn)驗(yàn)證
我們定義了兩個函數(shù),一個父函數(shù),一個子函數(shù),它們都實(shí)現(xiàn)了事務(wù)機(jī)制,父函數(shù)中嵌套了子函數(shù)。這兩個函數(shù)的作用都是在同一張表中插入一條數(shù)據(jù),通過改變父子函數(shù)的事務(wù)傳播行為以及它們是否拋出異常來驗(yàn)證以上七種傳播行為的正確性。結(jié)果如圖所示(null表示插入失敗):

源代碼
實(shí)體類 Transaction
package org.jz.transaction.chapter1;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("transaction")
public class Transaction {
@TableId(value = "`id`",type = IdType.AUTO)
private Integer id;
@TableField("`name`")
private String name;
@TableField("`group`")
private Integer group;
}
Mapper接口 TransactionMapper
package org.jz.transaction.chapter1;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TransactionMapper extends BaseMapper<Transaction> {
}
主程序類 Chapter1Application
package org.jz.transaction.chapter1;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@SpringBootApplication
public class Chapter1Application {
@Bean
@Scope("prototype")
public TransactionTemplate transactionTemplate(PlatformTransactionManager platformTransactionManager) {
return new TransactionTemplate(platformTransactionManager);
}
@Autowired
TransactionTemplate father;
@Autowired
TransactionTemplate son;
@Autowired
TransactionMapper transactionMapper;
public static void main(String[] args) {
SpringApplication.run(Chapter1Application.class, args);
}
@Data
private class Item {
private Integer fatherPropagation;
private Integer sonPropagation;
private Integer fatherThrowError;
private Integer sonThrowError;
private String result;
private String errorResult;
}
@PostConstruct
public void testPropagation() {
List<Item> items = new ArrayList<>();
//遍歷所有可能的情況
permute(items, new ArrayList<>(), Arrays.asList(7, 7, 2, 2), 0);
//傳播行為列表
List<String> propagation = Arrays.asList("required", "support", "mandatory", "requires_new", "not_support",
"never", "nested");
for (int i = 0; i < items.size(); i++) {
final int group = i;
Item item = items.get(i);
father.setPropagationBehavior(item.getFatherPropagation());
try {
father.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
//先插入一條父數(shù)據(jù)
transactionMapper.insert(new Transaction(null,"father", group));
son.setPropagationBehavior(item.getSonPropagation());
son.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
//插入一條子數(shù)據(jù)
transactionMapper.insert(new Transaction(null,"son", group));
if (item.getSonThrowError() > 0) {
throw new RuntimeException("子調(diào)用拋出異常");
}
}
});
if (item.getFatherThrowError() > 0) {
throw new RuntimeException("父調(diào)用拋出異常");
}
}
});
} catch (Exception e) {
//捕獲父類異常,防止停止運(yùn)行
log.info("捕獲 {}", e.getMessage());
item.setErrorResult(e.getMessage());
}
List<Transaction> transactions = transactionMapper.selectList(new LambdaQueryWrapper<Transaction>()
.eq(Transaction::getGroup, group));
boolean fatherRes = transactions.stream().anyMatch(a -> "father".equals(a.getName()));
boolean sonRes = transactions.stream().anyMatch(a -> "son".equals(a.getName()));
item.setResult(StrUtil.join(" | ", fatherRes ? "father" : null, sonRes ? "son" : null));
}
//清空表
transactionMapper.delete(new QueryWrapper<>());
//輸出為表格
ExcelWriter writer = ExcelUtil.getWriter(FileUtil.getUserHomePath() + "/Desktop/res.xlsx");
writer.addHeaderAlias("fatherPropagation", "父調(diào)用傳播行為");
writer.addHeaderAlias("sonPropagation", "子調(diào)用傳播行為");
writer.addHeaderAlias("fatherThrowError", "父調(diào)用拋出異常");
writer.addHeaderAlias("sonThrowError", "子調(diào)用拋出異常");
writer.addHeaderAlias("errorResult", "報錯輸出");
writer.addHeaderAlias("result", "結(jié)果");
List<Map<String, Object>> collect = items.stream().map(a -> {
Map<String, Object> map = BeanUtil.beanToMap(a);
map.put("fatherPropagation", propagation.get((Integer) map.get("fatherPropagation")));
map.put("sonPropagation", propagation.get((Integer) map.get("sonPropagation")));
return map;
}).collect(Collectors.toList());
writer.write(collect);
writer.close();
}
private void permute(List<Item> items, List<Integer> list, List<Integer> asList, int i) {
if (i == asList.size()) {
Item item = new Item();
List<Integer> copy = new ArrayList<>(list);
item.setFatherPropagation(copy.get(0));
item.setSonPropagation(copy.get(1));
item.setFatherThrowError(copy.get(2));
item.setSonThrowError(copy.get(3));
items.add(item);
return;
}
Integer loop = asList.get(i);
for (int k = 0; k < loop; k++) {
list.add(k);
permute(items, list, asList, i + 1);
list.remove(list.size() - 1);
}
}
}