SpringBoot 使用 @Transactional 注解配置事务

/**
springboot默认开启了事务,@EnableTransactionManagement
这注解有以属性proxyTargetClass,默认为false,表示使用jdk动态代理(对于不是居于接口的类,就会去使用cjlib代理)
如果设置为true,则表示使用cjlib做动态代理(不管是不是居于接口的类,都使用cjlib),可以联想jdk和cjlib使用场景,来分析
事物注解使用场景。@Transactional
*/
@EnableTransactionManagement
@SpringBootApplication
@EnableSwagger2
@MapperScan("com.ultrapower.show.web.dao")
public class CpsUltraCqDhApplication {

	public static void main(String[] args) {
		SpringApplication.run(CpsUltraCqDhApplication.class, args);
	}
}


在测试用列中,默认是开启回滚的,即不管怎么操作,最后都不会提交
@RunWith(SpringRunner.class)
@SpringBootTest()
public class UltraShowCqPcSpApplicationTests {

    @Autowired
    PcWxjkAlarmTabMapper pcWxjkAlarmTabMapper;

    
// @Rollback() 默认为true,即开启事物回滚,
// 如果是测试事物问题,设置为false
    @Test
    @Transactional
    @Rollback(false)
    public void contextLoads() {
    }


1、详细介绍
事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编程式和声明式的两种方式。
编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。本文将着重介绍基于 @Transactional 注解的事务管理。

@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认(包访问权限,子类都不可见)可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

@Transactional 注解应该只被应用到 public 方法上,这样理解:

jdk动态代理基于接口,接口类的接口默认为public,就不存在private等

如果采用cjlib动态代理,是把当前类作为父类,代理类是子类,去继承他,如果定义为private,代理类也就看不到这个方法,如果其他的应该可以,所以我的理解是,不能定义为private,其他的都可以,但都是没测试过哈

但是这里一般的业务逻辑来说,Service层都是新定义接口,在实现类里面操作,所以应该都是用jdk做动态代理

 

默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。

2、使用方法

:SpringBoot项目会自动配置一个 DataSourceTransactionManager,所以我们只需在方法(或者类)加上 @Transactional 注解,就自动纳入 Spring 的事务管理了

如下在方法加上 @Transactional 注解:

抛出异常之后,事务会自动回滚,数据不会插入到数据库。

3、Spring事务的默认回滚机制
Spring的事务管理默认是针对***unchecked exception**回滚,也就是默认对Error异常和RuntimeException异常以及其子类进行事务回滚,且必须抛出异常,若使用try-catch对其异常捕获则不会进行回滚!(Error异常和RuntimeException异常抛出时不需要方法调用throws或try-catch语句);而checked exception*** 则必须用try语句块进行处理或者把异常交给上级方法处理总之就是必须写代码处理它,所以必须在service捕获异常,然后再次抛出,这样事务方才起效。

5、@Transactional 注解属性介绍
value 和 transactionManager 属性
它们两个是一样的意思。当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。

propagation 属性
事务的传播行为,默认值为 Propagation.REQUIRED。
可选的值有:

Propagation.REQUIRED
如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。

如a方法和b方法都添加了注解,使用默认传播模式,则a方法内部调用b方法,会把两个方法的事务合并为一个事务。

这里又会存在问题,如果b方法内部抛了异常,而a方法catch了b方法的异常,那这个事务还能正常运行吗?
答案是不行!会抛出异常:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only,因为当ServiceB中抛出了一个异常以后,ServiceB会把当前的transaction标记为需要rollback。但是ServiceA中捕获了这个异常,并进行了处理,认为当前transaction应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException。

Propagation.SUPPORTS
如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

Propagation.MANDATORY
如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

Propagation.REQUIRES_NEW
重新创建一个新的事务,如果当前存在事务,暂停当前的事务。这个属性可以实现:
类A中的a方法加上默认注解@Transactional(propagation = Propagation.REQUIRED),类B中的b方法加上注解@Transactional(propagation = Propagation.REQUIRES_NEW),然后在a方法中调用b方法操作数据库,再在a方法最后抛出异常,会发现a方法中的b方法对数据库的操作没有回滚,因为Propagation.REQUIRES_NEW会暂停a方法的事务。

Propagation.NOT_SUPPORTED
以非事务的方式运行,如果当前存在事务,暂停当前的事务。

Propagation.NEVER
以非事务的方式运行,如果当前存在事务,则抛出异常。

Propagation.NESTED
和 Propagation.REQUIRED 效果一样。

isolation 属性
事务的隔离级别,默认值为 Isolation.DEFAULT。

可选的值有:

Isolation.DEFAULT
使用底层数据库默认的隔离级别。

Isolation.READ_UNCOMMITTED 读未提交

Isolation.READ_COMMITTED   读已提交

Isolation.REPEATABLE_READ  可重复读

Isolation.SERIALIZABLE  串行化

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

readOnly 属性
指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性
用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor 属性
抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

6、@Transactional事务几点注意
这里面有几点需要大家留意:
A. 一个功能是否要事务,必须纳入设计、编码考虑。不能仅仅完成了基本功能就ok。
B. 如果加了事务,必须做好开发环境测试(测试环境也尽量触发异常、测试回滚),确保事务生效。
C. 以下列了事务使用过程的注意事项,请大家留意。

1.不要在接口上声明@Transactional ,而要在具体类的方法上使用 @Transactional 注解,否则注解可能无效。

2.不要图省事,将@Transactional放置在类级的声明中,放在类声明,会使得所有方法都有事务。故@Transactional应该放在方法级别,不需要使用事务的方法,就不要放置事务,比如查询方法。否则对性能是有影响的。

3.使用了@Transactional的方法,对同一个类里面的方法调用, @Transactional无效。比如有一个类Test,它的一个方法A,A再调用Test本类的方法B(不管B是否public还是private),但A没有声明注解事务,而B有。则外部调用A之后,B的事务是不会起作用的。之所以B方法不生效原因同第7点,如果方法A调用Test2中的方法B,方法B的事务会生效

4.使用了@Transactional的方法,只能是public,@Transactional注解的方法都是被外部其他类调用才有效,故只能是public。道理和上面的有关联。故在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,但事务无效。最主要去引用的时候,编译不过,但在controller当中使用的时候,只支持public的方法,原因:会检查目标方法的修饰符是不是 public,若不是 public,就不会获取@Transactional 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理(allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers()))

5.spring的事务在抛异常的时候会回滚,如果是catch捕获了,事务无效。可以在catch里面加上throw new RuntimeException();

6.最后有个关键的一点:和锁同时使用需要注意:由于Spring事务是通过AOP实现的,所以在方法执行之前会有开启事务,之后会有提交事务逻辑。而synchronized代码块执行是在事务之内执行的,可以推断在synchronized代码块执行完时,事务还未提交,其他线程进入synchronized代码块后,读取的数据不是最新的。
所以必须使synchronized锁的范围大于事务控制的范围,把synchronized加到Controller层或者大于事务边界的调用层!

7.Propagation.REQUIRES_NEW 不生效

场景:OrderServiceImpl类有两个方法a/b,a方法去调用b方法,b放上添加Propagation.REQUIRES_NEW,然后controller调用a方法,

a方法执行b方法中的事务正常,但是a中其他代码报错,但是b的事务也回滚了,所以该配置没生效。

要使得配置生效,必须通过JDK动态代理或者CGLIB来调用B方法,而不能直接在A方法中调用B方法,查资料说自己注入自己,然后调用,但是会报错BindingException,这个时候可以把B方法提出来,放在另外一个类中,然后注入进来,在调用,即可。

之所以在同一个类里面没有生效,就是因为调用a的时候是调用a的代理方法,而a里面调用b方法是调用b的原生方法,而不是代理方法,所以事务注解没有生效,等于还都是a上面的事务注解生效,所以还是要想办法调用b的代理方法;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值