4.Spring 事务

Spring 事务

1.基本使用

(1)注解方式

主启动类:

@SpringBootApplication
@EnableTransactionManagement
public class TxApplcation{
}

业务逻辑类:@Transaction可以作用在类上,也可以作用在方法上,作用在类上代表对当前类所有public方法做事务管理

@Service
public class TxServiceImpl{
    
    @Autowired
    private TxMapper txMapper;
    
    @Transactional
    public void test(){
        txMapper.insert();
    }
}

(2)@Transactional注解6大属性

属性名是否必填默认值描述
timeout-1(秒为单位)超时控制,超过规定时间未结束事务将会报错
readonlyfalse事务是否只读,即只能进行查询操作
rollbackfor指定异常回滚,事务默认回滚运行时异常(RuntimeException)
norollbackfor指定异常不回滚
isolationDEFAULT事务隔离级别
propagationREQUIRED事务传播行为

(3)事务的传播行为

传播行为下事务的回滚:https://blog.csdn.net/LiZhen314/article/details/117373337

  1. 什么是事务传播行为:事务方法存在嵌套时,内部事务方法和外部事务方法是否是同一个事务(即同一个数据库连接)。【标注了@transaction注解的方法,才叫事务方法。事务的传播属性才有效。也就是说一个标注的方法,一个没标注的方法嵌套在一起,传播属性设置成req_new也没有什么用。】

  2. 七种事务传播行为

假设@Transactional注解作用在方法A上

属性值解释
REQUIRED如果有事务在运行,当前的方法A就在这个事务内运行,否则就开启一个新的事务,并在自己的事务内运行,默认传播行为
REQUIRED-NEW当前方法A必须启动新事务,并在自己的事务内运行,如果有事务正在运行,则将它挂起
SUPPROTS表示当前方法A应该运行在事务中,如果当前没有事务运行,则方法A只是一个普通方法
NOT_SUPPORTS表示当前方法A不应该运行在事务中,方法A只是一个普通方法。如果存在当前事务,在该方法运行期间,当前事务将被挂起。
MANDATORY当前的方法A必须运行在事务内部,如果没有正在运行的事务,就会抛出异常
NEVER当前方法A不应该运行在事务中,如果有运行的事务,就抛出异常
NESTED没用
  1. 图示传播行为

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gdeXZnuh-1686813203061)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220720151447215.png)]

4.Spring 的事务传播类型总结

  • PROPAGATION_NESTED(没什么用)

1、共用一个事务,但每一层都有独立的保存点,可以独立提交事务的回滚。
2、每层事务可以通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 独立设置是否回滚。当前层的回滚不会影响上层的回滚,但上层的回滚会影响下层的回滚。
3、如果是通过抛出异常导致的回滚,这个会触发整个事务的回滚(Sping的事务处理中异常的回滚处理和手动回滚走的不同的处理逻辑)。
4、如果下层回滚,最上层不回滚,则最终事务仍是作为正常提交成功的事务,仍会触发事务提交成功后的事件,并不会触发事务回滚的事件。即最终事务的状态由最顶层的事务决定。

  • PROPAGATION_REQUIRED(⭐)

1、共用一个事务,只能设置全局回滚或全局不回滚,不支持局部回滚。
2、如果有多层,只要有任意一层设置了回滚,则它的上一层及最上层也必须设置回滚,否则会抛出Transaction rolled back because it has been marked as rollback-only异常。
3、由于所有层都是统一的提交和回滚,因此不会出现部分提交的问题。

(4)几种事务失效的情况

  1. @Transactional没有作用在public方法上

⭐为什么只能是public方法

解答:这是因为事务底层在使用 Spring AOP 代理时,Spring 在调用 TransactionInterceptor 在目标方法执行前后进行拦截之前,会有个叫DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取 @Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。

AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute:

protected TransactionAttribute computeTransactionAttribute(Method method,
    Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;}

这个方法会检查目标方法的修饰符是不是 public,若不是 public,就不会获取@Transactional 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理。

  1. Spring AOP的自调用

解答:因为spring事务是基于aop的代理机制,当方法中调用this本身的方法时候即使在this的方法标明事务注解,但是事务注解会失效。因为调用this本身方法不走代理机制(因为this代表是目标对象不是代理对象,调用目标对象本身方法当然不走代理)。也可以说,aop切入会生成service的代理对象,直接调用被切入的方法是有效的。如果被切入的方法,嵌套在一个没有被切入的方法里,就没有用了。可以通过aopcontext或者getBean错误代码如下:

@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@@Service
public class Test{
    @Transactional
    @Override
    public void purchase(String username, String isbn) {
        this.update(username, isbn);
    }
 
    @Transactional
    public void update(String username, String isbn) {
        //1. 获取书的单价
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2. 更新数的库存
        bookShopDao.updateBookStock(isbn);
        //3. 更新用户余额
        bookShopDao.updateUserAccount(username, price);
    }
}

解决:使用AopContext获取到当前aop代理(AopContext:能够从当前容器的ThreadLocal中拿到暴露的aop代理对象)。需要在主启动类上加 @EnableAspectJAutoProxy(exposeProxy = true)。AopContext会把这个事务先提交

@Transactional
@Override
public void purchase(String username, String isbn) {
	((BookShopServiceImpl)AopContext.currentProxy()).update(username, isbn);
}

如果AopContext还是报错,那么使用第二种方法。getBean拿出来得是目标对象(this)的代理对象

RoleService roleService = SpringUtil.getBean(this.getClass());
roleService.事务方法()
  1. 内部方法声明了@Transaction注解,外部方法没有声明。(内层方法声明了,外层也需要。反之可以不需要)
  2. 在线程方法里面调用声明了@Transaction注解的方法:
    1. 因为线程不归spring管,所以不管线程所在代码块方法是否声明了@Transaction注解或者AopContext.currentProxy()都没有用,只有一个办法
    2. 这个办法就是,将线程内被调用的@Transaction方法,抽取到当前线程所在服务之外,即另外一个服务:https://blog.csdn.net/m0_69305074/article/details/124619777

2.声明式事务之原理

  1. IOC容器启动时,会去查看当前容器内是否有@EnableTransactionManagerment注解,如果有就会去拦截所有bean的创建,查看他们中是否有@Transactional注解,如果有这个注解就会利用Spring aop来为这些bean创建切面类生成代理对象,代理对象中会含有一个拦截器,叫做TransactionInterceptor
  2. TransactionInterceptor这个类会拦截bean中用@Transactional注解的public方法,会在目标方法执行前开启事务,在目标方法结束后提交/回滚事务

TransactionInterceptor实现MethodInterceptor可以看出,spring aop默认为事务使用cglib代理

import org.aopalliance.intercept.MethodInterceptor;

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
     @Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Work out the target class: may be {@code null}.
		// The TransactionAttributeSource should be passed the target class
		// as well as the method, which may be from an interface.
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// Adapt to TransactionAspectSupport's invokeWithinTransaction...
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}
}
  1. TransactionInterceptor这个类构造时要传入一个事务管理器PlatformTransactionManager,这个接口主要用于开启事务,提交事务,回滚事务。PlatformTransactionManager有多个实现类,用来对应多个环境
image-20220720164447074
  • 开启一个事务:getTransaction()

执行了getTransaction后,spring内部会执行一些操作,为了方便大家理解,咱们看看伪代码:将数据源datasource和connection映射起来放在了ThreadLocal中,ThreadLocal大家应该比较熟悉,用于在同一个线程中共享数据(线程私有内存);后面我们可以通过resources这个ThreadLocal获取datasource其对应的connection对象。

//有一个全局共享的threadLocal对象 resources
static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
//获取一个db的连接
DataSource datasource = platformTransactionManager.getDataSource();
Connection connection = datasource.getConnection();
//设置手动提交事务
connection.setAutoCommit(false);
Map<Object, Object> map = new HashMap<>();
map.put(datasource,connection);
resources.set(map);
  • 提交事务:commit()
  • 回滚事务:rollback()

ection();
//设置手动提交事务
connection.setAutoCommit(false);
Map<Object, Object> map = new HashMap<>();
map.put(datasource,connection);
resources.set(map);


* 提交事务:commit()
* 回滚事务:rollback()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值