【Spring】之事务管理与事务失效

目录



一、Spring 管理事务的两种方式


在项目系统的事务管理中,Spring 为我们提供了两种管理事务的方式:编程式事务声明式事务。下面将对这两种方式进行讲解介绍。

1、编程式事务

编程式事务:是指在代码中手动地管理事务的提交、回滚等操作,代码侵入性比较强,代码示例如下:

try {
	...
	// 手动提交事务
	transactionManager.commit(status);
} catch (Exception e) {
	// 发生异常,手动回滚
	transactionManager.rollback(status)
}

2、声明式事务

声明式事务是建立在 AOP 之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务的实现方式:

  • 1、基于 TXAOPxml 配置文件方式;
  • 2、基于 @Transactional 注解。

优点:

最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明,便可以将事务规则应用到业务逻辑中。

缺点:

和编程式事务相比,声明式事务唯一不足地方是:它的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别

总结:显然声明式事务要优于编程式事务,这正是 Spring 提倡的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的 POJO 对象,只要加上注解就可以获得完全的事务支持。


二、@Transactional 注解介绍


1、@Transactional 使用方法

@Transactional 用于声明事务的开启,可以作用在接口、类、类方法上:

  • 作用在类上: 当把 @Transactional 注解放在类上时,表示所有该类的 public 方法都配置相同的事务属性信息;
  • 作用在方法上: 当类上配置了@Transactional,方法也配置了@Transactional,则方法的事务会覆盖类的事务配置信息;
  • 作用在接口: 不推荐这种使用方法,因为一旦标注在 Interface 上并且配置了 Spring AOP 使用CGLib 动态代理,将会导致 @Transactional 注解失效。

2、@Transactional 属性介绍

1)propagation 属性

表示事务的传播行为,默认值为 Propagation.REQUIRED,可选属性值如下:

  • Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务(也就是说如果A方法和B方法都添加了注解,并且都使用的是该传播模式,则A方法内部调用B方法时,会把两个方法的事务合并为一个事务);
  • Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务(也就是说在同一个类中,A方法为 REQUIRED,B方法为 REQUIRED_NEW,A调用B的话,A的事务会被B暂停,相当于A没有事务);
  • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务;
  • Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常;
  • Propagation.NESTED:和 Propagation.REQUIRED 效果一样。

2)isolation 属性

表示事务的隔离级别,默认的属性值为 Isolation.DEFAULT,可选属性值如下:

  • Isolation.DEFAULT:使用底层数据库默认的隔离级别;
  • Isolation.READ_UNCOMMITTED :未提交读;
  • Isolation.READ_COMMITTED :已提交读;
  • Isolation.REPEATABLE_READ :可重复读;
  • Isolation.SERIALIZABLE :串型化。

3)rollbackFor 属性

用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。Spring 默认是要抛出了未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。

4)noRollbackFor 属性

指定哪些异常类型被抛出时不会回滚事务,可以指定多个异常类型。

5)timeout 属性

事务的超时时间,默认值为 -1(表示没有超时时间)。如果超过该时间限制但事务还没有完成,则自动回滚事务。

6)readOnly 属性

指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据操作,这些是不需要进行回滚操作的,所以可设置 readOnly 为 true。


三、@Transactional 事务失效场景


1、数据库引擎不支持事务

以 MySQL 为例,其 MyISAM 引擎是不支持事务的,只有 InnoDB 引擎才支持事务,所以要支持事务的话需要使用 InnoDB。

注意:从 MySQL 5.5 开始的默认存储引擎是:InnoDB,之前默认的都是:MyISAM。

2、类没有交给 Spring 管理

例如以下示例:

// @Service
public class MyService {
	@Transactional
	public void insert() {
		// do something
	}
}

如果把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,也就不会被 Spring 管理,事务自然就失效了。

3、修饰的方法不是 public 类型

如果 Transactional 注解应用在非 public 修饰的方法上,则 Transactional 将会失效。我们可以查看 Spring 的官方文档:

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

具体原理是:在 Spring AOP 代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource 的 computeTransactionAttribute 方法,获取 Transactional 注解的事务配置信息:

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 的属性配置信息。

注意protectedprivate 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

4、自身调用问题

情况1:没有事务的方法调用有事务的方法

@Service
public class MyService {

	public void A() {
		B();
	}
	
	@Transactional
	public void B() {
		// do something
	}
}

这种情况下,方法B的事务是不会生效的,因为 Spring 的事务是通过 Apring AOP 动态代理来实现的,这种情况下相当于是发生了自身调用,从而绕过了 Spring 的代理导致事务失效。默认情况下,只有在外部调用事务方法时,才会由 Spring 生成的代理对象来管理,这样事务才会生效。

情况2:一个事务被另一个事务暂停

@Service
public class MyService {

	@Transactional
	public void A() {
		B();
	}
	
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void B() {
		// do something
	}
}

这种情况下,由于A方法使用的是默认事务传播行为 Propagation.REQUIRED,而调用了B方法后,由于B方法使用的事务传播行为 Propagation.REQUIRES_NEW,会把A的事务暂停掉,相当于没有事务。这样就和第一种情况那样:没有事务的方法调用有事务的方法,事务不会生效。

针对以上问题的解决办法是:使用代理对象来调用事务方法,比如使用 AsprctJ:

public class MyService {

	@Transactional
	public void A() {
        // 使用动态代理调用本地其他事务
        MyService myService = (MyService) AopContext.currentProxy();
        myService.B();
    }
    
    @Transactional(propagation=Propagation.REQUIRED_NEW)
    public void B() {
        // ....
    }
}

5、主动失效事务

如果注解的属性 propagation 的属性值设置为:Propagation.NOT_SUPPORTEDPropagation.NEVER,表示不以事务运行:

@Service
public class MyService {

	@Transactional(propagation = Propagation.NOT_SUPPORTED)
	public void A() {
		// do something
	}
	
	@Transactional(propagation = Propagation.NEVER)
	public void B() {
		// do something
	}
}

这些情况都表示主动放弃事务,自然不会生效事务了。

6、异常被捕获

这是最常见的事务失效的场景:

@Service
public class MyService {

	@Transactional
	public void A() throws Exception {
		try {
			// do something
		} catch (Exception e) {
			log.error(e);
		}
	}
}

当方法中出现异常之后,事务管理器就会标记当前事务需要回滚,但由于我们手动对异常进行了捕获并处理,从而导致事务管理器认为当前事务是正常提交的,此时就出现了前后不一致的情况,就会导致抛出 UnexpectedRollbackException 异常。

Spring 的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行 commit or rollback,事务是否执行取决于是否抛出 runtime 异常。如果抛出 runtime exception 并在你的业务方法中没有 catch 到的话,事务会回滚。

在业务方法中一般不需要 catch 异常,如果非要 catch 一定要抛出 throw new RuntimeException(),或者注解中指定抛异常类型 @Transactional(rollbackFor=Exception.class),否则会导致事务失效。

7、异常类型错误

rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认是要抛出了未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。

在这里插入图片描述
但若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。更多异常相关知识可以了解我的另一篇博客:【Java基础】之深入讲解Java异常

8、数据源没有配置事务管理器

配置数据源方法如下:

@Bean
public PlatformTransactionManager prodTransactionManager(@Qualifier("prodDataSource") DataSource prodDataSource) {
    return new DataSourceTransactionManager(prodDataSource);
}

只有当前数据源配置了事务管理器才会生效事务。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值