暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring事务失效原因汇总

老胡的技术笔记 2021-04-06
488

01

简介

Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring 是无法提供事务功能的。真正的数据库层的事务提交和回滚是在binlog提交之后进行提交的 通过 redo log 来重做, undo log来回滚。

一般我们在程序里面使用的都是在方法上面加@Transactional 注解,这种属于声明式事务。

来看一下@Transactional的源码:

 * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute}
* (rolling back on {@link RuntimeException} and {@link Error} but not on checked
 * exceptions).

根据 @Transactional的说明支持RuntimeExceptionError但不支持checked exceptions

Checked Exception

Checked Exception 是必须在代码中进行恰当处理的 Exception,而且编译器会强制开发者对其进行处理,否则编译会不通过。你可以使用 catch 语句捕获这些 Exception 或者在方法声明处使用 throws 语句抛出该异常。

一般来说,Checked Exception 的发生主要是由于一些特殊情况没有考虑到,比如如果网络连接失败会抛出 IOException,但是我们的程序应该能够提前预料到这些可能发生的异常,并对其进行处理,这样程序在运行过程中才不会崩掉,这也是编译器强制开发者对 Checked Exception 进行处理的原因。假设在文件传输的过程中网络出现中断,这时候程序应该能够捕获到这种异常并进行处理(重新尝试传输文件)。

Unchecked Exception

Unchecked Exception 的发生有一些是由于开发者代码逻辑错误造成的,比如:NullPointerException 这种异常可以通过检查一个引用是否为 null 来进行避免。

但是也有一些 Unchecked Exception 出现并不是因为开发者程序的问题,这些 Exception 是 java.lang.Error 的子类。就像 OutOfMemoryError 可能发生在任意一个示例对象创建时,但我们不可能在每个对象实例创建时都使用 catch 块去捕获异常。因此,我们也就不可能预料这些异常的发生,编译器在编译时也无法检测到这些异常。

首先,java的异常分为Error和Exception。这两类都是接口Throwable的子类。Error及Exception及其子类之间的关系,大致可以用下图简述:

checkedunchecked在throwable的继承关系中体现为下图:

02

事务失效场景

第1种:数据库引擎不支持事务

这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。从 MySQL 5.5.5 开始的默认存储引擎是:InnoDB,之前默认的都是:MyISAM,所以这点要值得注意,底层引擎不支持事务。


第2种:非Spring容器管理的bean

示例:

//@Service
public class TestServiceImpl implements TestService {
@Transactional
@Override
public void test() {
    // todo 业务处理
}
}
如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。

第3种:方法不是 public 的

参考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.
使用代理时,您应该只将@Transactional注释应用于具有公共可见性的方法。
如果使用@Transactional注释对受保护的、私有的或包可见的方法进行注释,则不会引发错误,但带注释的方法不会显示配置的事务设置。
如果需要注释非公共方法,请考虑使用AspectJ(见下文)。

第4种:自身内部调用

示例:

@Override
public ReponseResult test2() {
MessageBO message = new MessageBO();
message.setMobile("157XXXXXXX");
Timestamp currenTimestamp = new Timestamp(System.currentTimeMillis());
message.setUpdateTime(currenTimestamp);
return test3( message);
}
@Transactional
public ReponseResult test3(MessageBO message){
messageDao.updateMessage(message);
log.info("更新数据库成功");
    log.error("test3开始出错");
int i = 1/0;
return ReponseResult.success();
 }

test2()方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 test3()方法,这种情况事务不会生效。Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.test3(),此时的test3方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效。

如果改成这样事务是生效的:

@Transactional
@Override
public ReponseResult test2()
{
MessageBO message = new MessageBO();
message.setMobile("157XXXXXXX");
Timestamp currenTimestamp = new Timestamp(System.currentTimeMillis());
message.setUpdateTime(currenTimestamp);
return test3( message);
}
@Transactional
public ReponseResult test3(MessageBO message)
{
messageDao.updateMessage(message);
log.info("更新数据库成功");
log.error("test3开始出错");
int i = 1/0;
return ReponseResult.success();
}

第5种:数据源没有配置事务管理

  @Override
  @Bean
  public PlatformTransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager(this.appDataSource);
   }

第6种:异常被捕获,但未被抛出

  @Transactional
@Override
public void test1() {
MessageBO message = new MessageBO();
message.setMobile("157XXXXXX");
Timestamp currenTimestamp = new Timestamp(System.currentTimeMillis());
message.setUpdateTime(currenTimestamp);
messageDao.updateMessage(message);
    log.info("更新数据库成功");
try {
log.error("test1开始出错");
int i = 1/0;
}catch (Exception e){
     //todo
    }
}

第7种:抛出的异常类型错误非RuntimeException

@Transactional
@Override
public void test1() {
MessageBO message = new MessageBO();
message.setMobile("15711291961");
Timestamp currenTimestamp = new Timestamp(System.currentTimeMillis());
message.setUpdateTime(currenTimestamp);
messageDao.updateMessage(message);
    log.info("更新数据库成功");


try {
log.error("test1开始出错");
int i = 1/0;
}catch (Exception e){
throw new Exception("失败");
}
return ReponseResult.success();
}

第8种:事务的传播行为设置问题



@Transactional(propagation= Propagation.NOT_SUPPORTED , rollbackFor = Exception.class)
@Override
public ReponseResult test1() {
MessageBO message = new MessageBO();
message.setMobile("157XXXXXX");
Timestamp currenTimestamp = new Timestamp(System.currentTimeMillis());
message.setUpdateTime(currenTimestamp);
messageDao.updateMessage(message);
    log.info("更新数据库成功");
try {
log.error("test1开始出错");
int i = 1/0;
}catch (Exception e){
throw new BusinessException(ResultCodeEnum.BUSINESS_ERROR, "失败:");
}
return ReponseResult.success();
}

当传播行为设置了PROPAGATION_NOT_SUPPORTEDPROPAGATION_NEVERPROPAGATION_SUPPORTS这三种时,就有可能存在事务不生效


END


文章转载自老胡的技术笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论