java事务传播机制一文搞懂。
使用:
1、引入spring的jdbc、数据库驱动,数据源
2、配置数据源,注入JdbcTemplate,启用事务管理,注入DataSourceTransactionManager
3、传播机制
@see Propagation#REQUIRED 支持当前事务,如果没有则新建一个事务,
例:a方法调用b方法,如果a方法有事务,则b加入a事务,如果a没有,则b新建一个事务
@see Propagation#SUPPORTS 支持当前事务,如果没有则以非事务运行
例:a方法调用b方法,如果a方法有事务,则b加入a事务,如果a没有,则b以非事务运行
@see Propagation#MANDATORY 支持当前事务,如果没有则抛出异常
例:a方法调用b方法,如果a方法有事务,则b加入a事务,如果a没有,则b抛出异常
@see Propagation#REQUIRES_NEW 创建一个新的事务,如果当前有事务,则挂起
例:a方法调用b方法,不论a有没有事务,b都会新建一个事务
@see Propagation#NOT_SUPPORTED 不支持事务,如果当前有事务则挂起
例:a方法调用b方法,不论a有没有事务,b都会以非事务方式运行
@see Propagation#NEVER 不支持事务,如果有则抛出异常
例:a方法调用b方法,如果a有事务,b会抛出异常
@see Propagation#NESTED 嵌套事务
例:a方法调用b方法,如果a有事务,b会开启一个内嵌事务。如果a没有事务,则b开启一个新的事务
4、代码测试
服务类:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Random;
/**
* @author liangjunhui
* @date Created in 2020-08-12 11:25
*/
@Service
public class UsersService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
UsersService usersService;
private int num = 9;
@Transactional(rollbackFor = Exception.class,timeout = 10)
public void requiredA(int a, int b, Propagation propagation) {
try {
insertUser(a);
} finally {
invoke(b, propagation);
}
}
@Transactional(rollbackFor = Exception.class)
public void requiredACatchB(int a, int b, Propagation propagation) {
try {
insertUser(a);
} finally {
try {
invoke(b, propagation);
} catch (Exception e) {
}
}
}
public void invoke(int b, Propagation propagation) {
switch (propagation) {
case REQUIRED:
usersService.requiredB(b);
break;
case SUPPORTS:
usersService.supportsB(b);
break;
case MANDATORY:
usersService.mandatoryB(b);
break;
case REQUIRES_NEW:
usersService.requiresNewB(b);
break;
case NOT_SUPPORTED:
usersService.notSupportedB(b);
break;
case NEVER:
usersService.neverB(b);
break;
case NESTED:
usersService.nestedB(b);
break;
default:
break;
}
}
@Transactional(rollbackFor = Exception.class)
public void requiredB(int b) {
insertUser(b);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
public void supportsB(int b) {
insertUser(b);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
public void mandatoryB(int b) {
insertUser(b);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void requiresNewB(int b) {
insertUser(b);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
public void notSupportedB(int b) {
insertUser(b);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
public void neverB(int b) {
insertUser(b);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public void nestedB(int b) {
insertUser(b);
}
/**
* 当i为0的时候,会发生异常
* @param i
* @return
*/
private int insertUser(int i) {
String methodName = Thread.currentThread().getStackTrace()[2].getMethodName();
String name = methodName + new Random().nextInt();
int update = jdbcTemplate.update("insert into users(`userName`,`passWord`) values (?,?)", new Object[]{name, "123456"});
int res = num / i;
return update;
}
}
service
注意:有多个事务注解标注的方法相互调用的时候,一定要从IOC容器中取对象,否则不生效。原因是apc默认创建的对象调用目标方法是通过实例化初始化之后的bean,这个bean是未代理增强的,创建代理是BeanPostProcessor实现类负责的,通过初始化的后置处理器包装,然后将包装好的bean放入IOC容器中,这个代理对象持有未代理之前的引用,所以说要从容器中取。
测试类:
/**
* @author liangjunhui
* @date Created in 2020-08-12 10:58
*/
public class TestTx extends BaseTest {
/**
* 测试所有的事务传播,该情况调用方不处理被调用方的异常
* 测试结果:
* ----------------------测试REQUIRED开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功0次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 当前有事务,a成功,b失败共执行2次,成功0次
* 当前有事务,a失败,b成功共执行2次,成功0次
* 当前无事务,失败共执行1次,成功0次
* 当前无事务,成功共执行1次,成功1次
* ----------------------测试REQUIRED结束-------------------------
* ----------------------测试SUPPORTS开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功0次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 当前有事务,a成功,b失败共执行2次,成功0次
* 当前有事务,a失败,b成功共执行2次,成功0次
* 当前无事务,失败共执行1次,成功1次
* 当前无事务,成功共执行1次,成功1次
* ----------------------测试SUPPORTS结束-------------------------
* ----------------------测试MANDATORY开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功0次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 当前有事务,a成功,b失败共执行2次,成功0次
* 当前有事务,a失败,b成功共执行2次,成功0次
* 发生异常:No existing transaction found for transaction marked with propagation 'mandatory'
* 当前无事务,失败共执行1次,成功0次
* 发生异常:No existing transaction found for transaction marked with propagation 'mandatory'
* 当前无事务,成功共执行1次,成功0次
* ----------------------测试MANDATORY结束-------------------------
* ----------------------测试REQUIRES_NEW开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功0次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 当前有事务,a成功,b失败共执行2次,成功0次
* 当前有事务,a失败,b成功共执行2次,成功1次
* 当前无事务,失败共执行1次,成功0次
* 当前无事务,成功共执行1次,成功1次
* ----------------------测试REQUIRES_NEW结束-------------------------
* ----------------------测试NOT_SUPPORTED开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功1次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 当前有事务,a成功,b失败共执行2次,成功1次
* 当前有事务,a失败,b成功共执行2次,成功1次
* 当前无事务,失败共执行1次,成功1次
* 当前无事务,成功共执行1次,成功1次
* ----------------------测试NOT_SUPPORTED结束-------------------------
* ----------------------测试NEVER开始-------------------------
* 发生异常:Existing transaction found for transaction marked with propagation 'never'
* 当前有事务,a失败,b失败共执行2次,成功0次
* 发生异常:Existing transaction found for transaction marked with propagation 'never'
* 当前有事务,a成功,b成功共执行2次,成功0次
* 发生异常:Existing transaction found for transaction marked with propagation 'never'
* 当前有事务,a成功,b失败共执行2次,成功0次
* 发生异常:Existing transaction found for transaction marked with propagation 'never'
* 当前有事务,a失败,b成功共执行2次,成功0次
* 当前无事务,失败共执行1次,成功1次
* 当前无事务,成功共执行1次,成功1次
* ----------------------测试NEVER结束-------------------------
* ----------------------测试NESTED开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功0次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 当前有事务,a成功,b失败共执行2次,成功0次
* 当前有事务,a失败,b成功共执行2次,成功0次
* 当前无事务,失败共执行1次,成功0次
* 当前无事务,成功共执行1次,成功1次
* ----------------------测试NESTED结束-------------------------
*/
@Test
public void testTx(){
for (Propagation propagation:Propagation.values()){
System.out.println("----------------------测试"+propagation.name()+"开始-------------------------");
testTxCommon(propagation);
System.out.println("----------------------测试"+propagation.name()+"结束-------------------------");
}
}
/**
* 测试所有的事务传播,该情况调用方处理被调用方的异常
* 测试结果:
* ----------------------测试REQUIRED开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功0次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 发生异常:Transaction rolled back because it has been marked as rollback-only
* 当前有事务,a成功,b失败共执行2次,成功0次
* 当前有事务,a失败,b成功共执行2次,成功0次
* ----------------------测试REQUIRED结束-------------------------
* ----------------------测试SUPPORTS开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功0次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 发生异常:Transaction rolled back because it has been marked as rollback-only
* 当前有事务,a成功,b失败共执行2次,成功0次
* 当前有事务,a失败,b成功共执行2次,成功0次
* ----------------------测试SUPPORTS结束-------------------------
* ----------------------测试MANDATORY开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功0次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 发生异常:Transaction rolled back because it has been marked as rollback-only
* 当前有事务,a成功,b失败共执行2次,成功0次
* 当前有事务,a失败,b成功共执行2次,成功0次
* ----------------------测试MANDATORY结束-------------------------
* ----------------------测试REQUIRES_NEW开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功0次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 当前有事务,a成功,b失败共执行2次,成功1次
* 当前有事务,a失败,b成功共执行2次,成功1次
* ----------------------测试REQUIRES_NEW结束-------------------------
* ----------------------测试NOT_SUPPORTED开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功1次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 当前有事务,a成功,b失败共执行2次,成功2次
* 当前有事务,a失败,b成功共执行2次,成功1次
* ----------------------测试NOT_SUPPORTED结束-------------------------
* ----------------------测试NEVER开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功0次
* 当前有事务,a成功,b成功共执行2次,成功1次
* 当前有事务,a成功,b失败共执行2次,成功1次
* 当前有事务,a失败,b成功共执行2次,成功0次
* ----------------------测试NEVER结束-------------------------
* ----------------------测试NESTED开始-------------------------
* 当前有事务,a失败,b失败共执行2次,成功0次
* 当前有事务,a成功,b成功共执行2次,成功2次
* 当前有事务,a成功,b失败共执行2次,成功1次
* 当前有事务,a失败,b成功共执行2次,成功0次
* ----------------------测试NESTED结束-------------------------
*/
@Test
public void testTxCacheB(){
for (Propagation propagation:Propagation.values()){
System.out.println("----------------------测试"+propagation.name()+"开始-------------------------");
testTxCommonCacheB(propagation);
System.out.println("----------------------测试"+propagation.name()+"结束-------------------------");
}
}
private void testTxCommon(Propagation propagation) {
AllStatus[] values = AllStatus.values();
for (AllStatus status : values) {
String[] strArr = status.getStatus().split("");
int before = testCount();
int a = Integer.parseInt(strArr[0]);
try {
if (strArr.length > 1) {
int b = Integer.parseInt(strArr[1]);
usersService.requiredA(a, b, propagation);
} else {
usersService.invoke(a, propagation);
}
}catch (ArithmeticException ae){
// e.printStackTrace();
}catch (Exception e) {
System.out.println("发生异常:"+e.getMessage());
}
int after = testCount();
System.out.println(status.getMsg() + "共执行" + strArr.length + "次,成功" + (after - before) + "次");
}
}
private void testTxCommonCacheB(Propagation propagation) {
AllStatus[] values = AllStatus.values();
for (AllStatus status : values) {
String[] strArr = status.getStatus().split("");
int before = testCount();
int a = Integer.parseInt(strArr[0]);
try {
if (strArr.length > 1) {
int b = Integer.parseInt(strArr[1]);
usersService.requiredACatchB(a, b, propagation);
} else {
continue;
}
}catch (ArithmeticException ae){
// e.printStackTrace();
}catch (Exception e) {
System.out.println("发生异常:"+e.getMessage());
}
int after = testCount();
System.out.println(status.getMsg() + "共执行" + strArr.length + "次,成功" + (after - before) + "次");
}
}
public int testCount() {
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
Map<String, Object> countMap = jdbcTemplate.queryForMap("select count(1) as count from users");
return Integer.parseInt(countMap.get("count").toString());
}
@Before
public void before() {
getContext(TxConfig.class);
usersService = context.getBean(UsersService.class);
}
private UsersService usersService;
enum AllStatus {
ALL_FAILE("00", "当前有事务,a失败,b失败"),
ALL_SUCCESS("11", "当前有事务,a成功,b成功"),
SUCCESS_FAILE("10", "当前有事务,a成功,b失败"),
FAILE_SUCCESS("01", "当前有事务,a失败,b成功"),
FAILE("0", "当前无事务,失败"),
SUCCESS("1", "当前无事务,成功"),;
private String status;
private String msg;
AllStatus(String status, String msg) {
this.status = status;
this.msg = msg;
}
public String getStatus() {
return status;
}
public String getMsg() {
return msg;
}
}
}
测试类
5、REQUIRED、REQUIRES_NEW、NESTED 之间的区别:
REQUIRED是a,b公用一个事务,a、b任一一个发生异常,事务都会回滚,即使是a未发生异常且处理了b的异常;
NESTED是a、b为父子事务,如果b发生了异常,a处理了b的异常,这样只有b会回滚,如果a发生了异常,则回滚。
父影响子,子不影响父。
REQUIREs_NEW是a、b只会影响到自己,即只要a或b执行完没有发生异常,事务就回提交。