Spring声明式事务@Transactional

19 篇文章 1 订阅

spring中事务分为声明式事务和编程式事务,本文主要介绍声明式事务

前提:
1、Spring事务基于AOP代理实现
2、不能解决不同服务之间调用的场景-分布式事务

一、声明式事务@Transactional参数

在这里插入图片描述

二、事务的七种传播机制

  • REQUIRED(必需)

如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常用的事务传播行为。

  • SUPPORTS(支持)

如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。

  • MANDATORY(强制)

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

  • REQUIRES_NEW(需要新的)

总是开启一个新的事务,如果当前存在事务,则把当前事务挂起。

  • NOT_SUPPORTED(不支持)

总是非事务地执行,并挂起任何存在的事务。意思当前方法不会使用任何事务,但是不影响其他方法事务

  • NEVER(从不)

总是非事务地执行,如果存在一个事务,则抛出异常。

  • NESTED(嵌套)

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则效果同REQUIRED。它使用了一个单独的事务,这个事务是一个嵌套的事务,可以独立于包装事务进行单独的提交和回滚。

使用举例:

@Transactional(propagation = Propagation.NESTED)
public void performService() {
    // 业务逻辑
}

三、事务隔离级别

  • Read Uncommitted(读未提交)
  • Read Committed(读已提交)
  • Repeatable Read(可重复读)(默认)
  • Serializable(可串行化)

四、Spring事务未生效场景

1、抛出事务不支持的异常

原理:
Spring事务默认支持RuntimeException异常,抛出的异常为RuntimeException异常及其子类异常事务均可生效,而我们日常常见的异常基本都继承自RuntimeException,所以无需指定异常类型事务也能生效。但若手动抛出Exception异常,而Exception是RuntimeException的父类,会导致事务不生效。
解决方案:

1.指定Spring事务异常捕获类型
@Transactional(rollbackFor = Exception.class)

2.抛出Spring事务支持的异常类型
throw new RuntimeException("手动抛出运行时异常");

2、使用了try catch

原理:
异常被try catch块捕获,导致事务失效

解决方案:

在catch中抛出Spring事务支持的异常。

3、添加事务的方法必须是public,并且不能带有static、final关键字

Spring使用基于代理的AOP来实现声明式事务,代理只能拦截公共方法的调用。
并且基于代理的AOP无法拦截静态方法和final方法的调用

  • public 方法:使 AOP 能够访问和拦截方法,以进行事务管理。
  • 避免 static:因为事务与对象的状态和操作相关,静态方法不具备这种特性。
  • 排除 final:限制了方法的可覆盖性,可能影响事务管理的灵活性。

4、类未被Spring管理

原理:
Spring实现对象的动态代理,首先这个对象要交由Spring管理。

解决方案:

将类交由Spring管理,可添加@Service注解,或使用其他能够注册成Spring Bean的注解或方法。

5、数据表不支持事务

原理:
Spring事务基于数据库事务实现,有些数据表本身不支持事务,如MySql的MyISAM引擎,事务自然不生效。

解决方案:

将数据表改用支持事务的引擎,如MySql的InnoDB引擎。

6、Spring事务传播级别设置为不支持事务

原理:
@Transactional(propagation = Propagation.NOT_SUPPORTED) 不支持事务,若存在事务则挂起
@Transactional(propagation = Propagation.NEVER) 不使用事务,若存在事务则抛异常

解决方案:

使用Spring默认的传播级别(PROPAGATION_REQUIRED),或其他支持事务的传播级别。

7、未开启事务

解决方案:

@EnableTransactionManagement开启事务,Spring boot已自动装配,无需显示使用此注解。

8、多线程调用

@Transactional注解只能对当前线程的事务进行管理,而对于多个线程之间的事务协调和管理,需要使用分布式事务进行控制。可以使用Spring Cloud提供的分布式事务管理器,如Seata、TCC等,来协调多个线程之间的事务,确保事务的一致性。

五、事务使用分析

1、多方法事务范围分析

1、如下三个方法,在方法1中声明事务,只要方法1事务生效,方法2和方法3事务也会生效

@Transactional(rollbackFor = Exception.class)
public void method1() {
method2();
}

public void method2() {
method3();
}

public void method3() {
}

在方法1中事务可以看成这样,即方法1开始时便开启了事务,直到方法1结束的时候才提交事务。
其中方法2是包含在方法1的事务里面的,因此方法2和方法3都属于这个事务

public void method1() {
开启事务。。。
method2();
事务提交。。。
}

注:这里方法2和方法3无论是直接调用,还是代理调用都成立

2、这里设定方法1中是通过代理类调用的方法2,所以方法2中事务生效

public void method1() {
method2();
}

@Transactional(rollbackFor = Exception.class)
public void method2() {
method3();
}

public void method3() {
}

因此可以看成下面这样,所以方法1中内容未被事务管理,方法2和方法3中会事务生效

public void method1() {
	...
	method2(){
		开启事务。。。
		method3();
		事务提交。。。
	}
	...
}

2、方法1 直接调用 方法2,在方法2 中添加事务

如下通过bean实例调用 login1,事务是否能生效?

答案:login1 和 login2 事务均不生效。

在Spring框架中,当你配置了事务管理器并使用了@Transactional注解来声明事务时,Spring通过使用AOP代理(默认情况下,是基于JDK的动态代理或CGLIB代理)将你的方法包装起来,用以控制事务的边界(即事务的开始和结束)。只有通过这个代理对象调用的方法,Spring才会管理其事务行为。因为代理的增强逻辑只会被应用在通过代理对象调用的方法上。

login1()在执行的时候调用了login2(),这时调用是直接的方法调用,并不通过代理对象。因此,即使login2()被声明为@Transactional,Spring也不会为其创建新的事务。在这种情况下,login2()就会参与到login1()的事务中,如果login1()没有事务,那么login2()也不会有事务。

	@PostMapping("/ceshi")
	public void login1() {
        AdminUserDO adminUserDO = adminUserMapper.selectById(131L);
        adminUserDO.setNickname("哈哈");
        adminUserMapper.updateById(adminUserDO);
        login2();  // 调用方法2
        if (1/0==1){
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void login2() {
        AdminUserDO adminUserDO = adminUserMapper.selectById(146L);
        adminUserDO.setNickname("嘻嘻");
        adminUserMapper.updateById(adminUserDO);
    }

3、方法1 调用 方法2,在方法1 中添加事务

如下通过bean实例调用 login1,事务是否能生效?
答案:login1 和 login2 事务 均能生效 ; 因为 login1 是通过代理对象调用,所以事务能生效,同事login2 也会参与到login1 中的事务,而不是因为他本身

login2中事务生效是因为它上面加了事务注解吗?
答案:不是;不管 login2 上面也没有事务注解都会被放入事务中。因为login1 是直接调用 login2,所以 login2中的事务注解未生效,而是引用了方法1中事务

	@PostMapping("/ceshi")
	@Transactional(rollbackFor = Exception.class)
	public void login1() {
        AdminUserDO adminUserDO = adminUserMapper.selectById(131L);
        adminUserDO.setNickname("哈哈");
        adminUserMapper.updateById(adminUserDO);
        login2();  // 调用方法2
        if (1/0==1){
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void login2() {
        AdminUserDO adminUserDO = adminUserMapper.selectById(146L);
        adminUserDO.setNickname("嘻嘻");
        adminUserMapper.updateById(adminUserDO);
    }

4、方法1 中通过 bean 调用方法2

如下 login1 中 事务不会生效,login 2中事务会生效。
因为 login1 是通过代理方式调用 login2.而login2中声明了事务。所以login2会事务回滚。而login1没有声明事务,所以不会回滚。其实可以看成调用方法2之前开启事务,方法2结束后提交事务,因此只有方法2中事务回滚

	@Autowired
    private AuthService authService ;

	@PostMapping("/ceshi")
	public void login1() {
        AdminUserDO adminUserDO = adminUserMapper.selectById(131L);
        adminUserDO.setNickname("哈哈");
        adminUserMapper.updateById(adminUserDO);
        authService.login2(); // 调用方法2
        if (1/0==1){
        }
    }
 	@Transactional(rollbackFor = Exception.class)
    public void login2() {
        AdminUserDO adminUserDO = adminUserMapper.selectById(146L);
        adminUserDO.setNickname("嘻嘻");
        adminUserMapper.updateById(adminUserDO);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值