@transaction使自定义注解失效_使用Spring的事务时,为什么我的事务失效了?

本文共xxxx字,将列举Spring事务失效的几种实际场景,帮助大家避免和发现此类问题,并保证事务的正常启用。

@transaction使自定义注解失效_使用Spring的事务时,为什么我的事务失效了?_第1张图片

前言

Spring可谓是目前最流行的Java开发框架了,除了为开发者提供便利和强大的开发方式外,它也整合了数据库的事务功能,形成了一套事务管理的框架。

一般情况下,在SpringBoot强大的注解模式下,我们都是采用@Transaction的注解进行事务在方法层面的开启。

但很多情况下,会发现,咦,自己明明配置了注解,也启动了配置,为何事务不生效呢?

下面,我们就来列举下常见的几种事务失效场景。

事务失效场景总结

  • 数据库引擎本身不支持事务

当使用的数据库引擎不支持事务的时候,那么Spring即使开启了事务,也不会生效,要知道,Spring的事务管理实际上是对数据库事务的一次封装。

这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。

从 MySQL 5.5.5 开始的默认存储引擎是:InnoDB,之前默认的都是:MyISAM,所以这点要值得注意,底层引擎不支持事务再怎么搞都是白搭。

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

当程序引用的数据源,没有配置事务管理器时,便相当于没有事务管理,自然也不会生效

@Bean

public PlatformTransactionManager transactionManager(DataSource dataSource) {

return new DataSourceTransactionManager(dataSource);

}

  • 方法不是公用的public方法

Spring官方表示:@Transaction注解需要用在public方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启AspectJ的代理模式。

  • 该Bean没有被Spring创建

也就是说使用了@Transaction注解的方法,其所在的类没有被Spring创建为一个Bean,此时是不会被Spring所管理的,那么事务自然也就失效了。

  • 在事务传播中设置了不支持的模式

之前有一篇文章讲述了事务传播机制,其中有不支持事务的传播机制,比如说NOT_SUPPORTED。也就是说,当我们在@Transaction注解中,使用了不支持事务的传播机制时,此时事务是不会生效的。

比如:

@Service

public class TestServiceImpl implements TestService {

@Transactional(propagation = Propagation.NOT_SUPPORTED)

public void testTran(String arg) {

// doSomething

}

}

可见,当配置属性propagation为Propagation.NOT_SUPPORTED时,表示主动不以事务运行。

  • 异常捕获且不抛出

Spring的事务管理,实际上就是通过代理,在配置了@Transaction的方法前后拦截,在捕获到对应类型的异常时进行回滚。那么如果我们在方法里进行手动的try..catch,并且将捕获到的异常给吃了,那么Spring代理便无法得知方法调用的异常情况,就无法进行事务异常时的回滚操作。

比如:

@Service

public class TestServiceImpl implements TestService {

@Transactional

public void testTran(String arg) {

try{

// doSomething

} catch (Exception e){

// 捕获处理不抛出

}

}

  • 异常类型错误

Spring的事务异常回滚,有自己制定的异常类型,当满足了条件才会进行回滚,默认的异常类型为RuntimeException,那么此时如果我们抛出了非这个类型的异常时,同样是不会进行回滚的。

比如:

@Service

public class TestServiceImpl implements TestService {

@Transactional

public void testTran(String arg) {

try{

// doSomething

} catch (Exception e){

throw new Exception(....)

}

}

那么如何解决这个问题呢,我们可以通过配置@Transaction里的rollbackFor属性,指定为Exception.class来解决问题,但建议针对业务性异常,最好自己在程序里创建继承于RuntimeException的异常类,并显式抛出。

  • 自身调用问题

来看下下面这个代码:

@Service

public class TestServiceImpl implements TestService {

public void test(String arg){

this.testTran(arg);

}

@Transactional

public void testTran(String arg) {

try{

// doSomething

} catch (Exception e){

throw new RuntimeException(....)

}

}

当我们调用test()这个没有加事务的方法的时候,其调用的testTran()方法若是出现了异常,此时testTran()所启用的事务会回滚吗?

答案是不会的。

再看下面这个代码:

@Service

public class TestServiceImpl implements TestService {

@Transactional

public void test(String arg){

this.testTran(arg);

}

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void testTran(String arg) {

try{

// doSomething

} catch (Exception e){

throw new RuntimeException(....)

}

}

我们为test()方法开启事务,并且对其调用的方法,我们配置传播属性为——创建一个新事务。

此时这个新开的事务会生效吗?

答案也是不会的。

之所以不会,是因为它们发生了自身调用。这个问题可谓是许多人都会碰到的一种失效情况,大多数方法,我们会出现调用的情况,当调用了自身类的方法时,由于此时没有经过Spring的代理类,也就是内部调用了事务方法,这种情况将导致该调用方法的事务失效。

那么如何解决自身调用出现的事务失效问题呢?

  • 一种是在类中再注入自己,调用时通过注入的这个bean来进行方法调用,此时会由Spring的代理类进行方法调用,事务将生效,缺点是不优雅。
  • 一种是将调用方法写到另一个bean中,注入该bean再去调用,缺点无疑是将相同业务的代码拆分,导致复杂度增高。
  • 一种是通过AopContext.currentProxy()来获取当前代理,转换为当前类后进行方法调用。比如

((TestService)AopContext.currentProxy()).testTran(arg);

这种方式是官方为我们提出的解决方案,也建议使用这种方案,注意,使用AopContext.currentProxy()是需要将expose-proxy设置为true才能生效的。

Tip

以上列举出来了8种常见的事务失效场景和部分解决方案,其中最常见的便是“异常不抛出”、“异常类型不正确”以及“自身调用”这三个问题。

你可能感兴趣的:(@transaction使自定义注解失效_使用Spring的事务时,为什么我的事务失效了?)