前言
SpringBoot下想要使用事务非常简单,只需要在Service的类或方法上面加上一个@Transactional注解即可实现失败自动回滚。大部分情况下,默认的@Transactional就能很好的满足需求了,但是更加深入的了解@Transactional还是很有必要的。
传播行为
在@Transactional注解中,可以propagation属性用来配置事务传播,支持7种不同的传播机制
REQUIRED:业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务。这是spring默 认的传播行为。
NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。
REQUIRES_NEW:不管是否存在事务,该方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。
MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。
SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。
NEVER:该方法绝对不能在事务范围内执行。如果在就抛异常。只有该方法没有关联到任何事务,才正常执行。
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。
实验验证
我们定义了两个函数,一个父函数,一个子函数,它们都实现了事务机制,父函数中嵌套了子函数。这两个函数的作用都是在同一张表中插入一条数据,通过改变父子函数的事务传播行为以及它们是否抛出异常来验证以上七种传播行为的正确性。结果如图所示(null表示插入失败):
源代码
实体类 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) {
//先插入一条父数据
transactionMapper.insert(new Transaction(null,"father", group));
son.setPropagationBehavior(item.getSonPropagation());
son.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
//插入一条子数据
transactionMapper.insert(new Transaction(null,"son", group));
if (item.getSonThrowError() > 0) {
throw new RuntimeException("子调用抛出异常");
}
}
});
if (item.getFatherThrowError() > 0) {
throw new RuntimeException("父调用抛出异常");
}
}
});
} catch (Exception e) {
//捕获父类异常,防止停止运行
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", "父调用传播行为");
writer.addHeaderAlias("sonPropagation", "子调用传播行为");
writer.addHeaderAlias("fatherThrowError", "父调用抛出异常");
writer.addHeaderAlias("sonThrowError", "子调用抛出异常");
writer.addHeaderAlias("errorResult", "报错输出");
writer.addHeaderAlias("result", "结果");
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);
}
}
}