草庐IT

Spring事务(四)-事务失效场景

Y.. 2023-04-18 原文

有时候,我们明明在类或者方法上添加了@Transactional注解,却发现方法并没有按事务处理。其实,以下场景会导致事务失效。

1、事务方法所在的类没有加载到Spring IOC容器中。

Spring声明式事务的实现完全依赖于Spring的AOP代理机制,未被Spring管理的类中的方法不受Spring的AOP代理管理,因此,声明式事务失效。

2、方法没有被public修饰。

众所周知,java的访问权限修饰符有:private、default、protected、public四种,但是@Transactional注解只能作用于public修饰的方法上。之所以会失效是因为在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 的属性配置信息。

注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错。

3、在同一个类中的方法调用。

假如在同一个类中有A、B两个方法,如下:

@Service
public class UserServiceImpl {

    @Autowired
    UserMapper userMapper;

    public void A() {
        B();
    }

    @Transactional
    public void B() {
        userMapper.deleteById(1);
        int i = 10 / 0; //模拟发生异常
    }
    
}

像上面的代码,B方法使用@Transactional注解标注,在A方法中调用了B方法,在外部调用A方法时,B方法的事务不会生效。这是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

那么如果确实在同一类中调用事务方法怎么办呢?有以下3种方法解决:

  • 引入自身bean
@Service
public class UserServiceImpl {

    @Autowired
    UserMapper userMapper;

    @Autowired
    UserServiceImpl userServiceImpl;

    public void A() {
        userServiceImpl.B();
    }

    @Transactional
    public void B() {
        userMapper.deleteById(1);
        int i = 10 / 0; //模拟发生异常
    }

}
  • 通过ApplicationContext引入bean
@Service
public class UserServiceImpl {

    @Autowired
    UserMapper userMapper;

    @Autowired
    ApplicationContext applicationContext;

    public void A() {
        ((UserServiceImpl) applicationContext.getBean("userServiceImpl")).B();
    }

    @Transactional
    public void B() {
        userMapper.deleteById(1);
        int i = 10 / 0; //模拟发生异常
    }

}
  • 通过AopContext获取当前代理类

在启动类上添加注解@EnableAspectJAutoProxy(exposeProxy = true),表示是否对外暴露代理对象,即是否可以获取AopContext。然后,在业务类上使用AopContext。

@Service
public class UserServiceImpl {

    @Autowired
    UserMapper userMapper;

    public void A() {
        ((UserServiceImpl) AopContext.currentProxy()).B();
    }

    @Transactional
    public void B() {
        userMapper.deleteById(1);
        int i = 10 / 0; //模拟发生异常
    }

}

4、方法的事务传播类型不支持事务。

若propagation属性设置如下三种事务传播行为,事务将不会发生回滚。

  • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  • NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

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

5、不正确地捕获异常。

使用了try-catch代码块将异常捕捉了,没有向上抛出异常,事务不会回滚。

@Transactional
private void A() throws Exception {
    @Autowired
    UserMapper userMapper;
    
    @Autowired
    B b;
    
    try {
        //A方法插入数据
        User u = New User();
        u.setId(1);
        u.setName("张三");
        userMapper.insert(u);

        /**
         * 这里调用外部方法插入数据
         */
        b.insert();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

上面的代码,如果B类insert方法内部抛了异常,而A方法此时try catch了B类方法的异常,那么,事务并不会回滚,而且会抛出如下异常:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

因为当B类insert方法中抛出了一个异常以后,标识当前事务需要rollback。但是A方法中由于手动的捕获这个异常并进行处理,A方法认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。

6、属性rollbackFor设置错误。

rollbackFor可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务。其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要设置 rollbackFor属性。

7、数据库不支持事务。

事务本来就是数据库的功能,如果数据库本身不支持事务,那任凭代码上如何设置也是没用的。以MySQL为例,InnoDB引擎是支持事务的,而像MyISAM、MEMORY等是不支持事务的。从MySQL5.5.5开始默认的存储引擎是InnoDB,之前默认都是MyISAM。

8、方法使用final修饰。

如果一个方法不想被子类重写,那么我们就可以把他写成final修饰的方法。如果事务方法使用final修饰,那么Spring AOP就无法在代理类中重写该方法,事务就不会生效。同样的,static修饰的方法也无法通过代理变成事务方法。

9、未开启事务。

如果是SpringBoot项目,那么SpringBoot通过DataSourceTransactionManagerAutoConfiguration自动配置类帮我们开启了事务。如果是传统的Spring项目,则需要我们自己配置。在Spring配置文件配置如下:

<!--事务管理器配置-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="insert*" propagation="REQUIRED"/>
        <!-- 所有insert开头的方法,以下同理 -->
        <tx:method name="update*" propagation="REQUIRED"/>
        <tx:method name="delete*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true" expose-proxy="true">
    <!-- 只对业务逻辑层实施事务 -->
    <aop:pointcut id="txPointcut"
                      expression="execution( * com.posun..service.impl..*+.*(..)) || execution(* com.posun..task..*+.*(..))
                       || execution(* com.posun.report.ReportJdbc.*(..)) "/>
    <aop:advisor id="txAdvisor" advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

或者,使用注解的方式。

<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 注解式事务声明配置-->
<tx:annotation-driven transaction-manager="transactionManager" />

10、多线程调用

@Service
public class UserServiceImpl {

    @Autowired
    UserMapper userMapper;

    @Transactional
    public void A() {
        userMapper.deleteById(1);
        new Thread(()->{
            userMapper.deleteById(2);
            int i = 10/0; //模拟发生异常        
            }).start();
    }

}

以上代码,A方法中,启动了一个新的线程,并在新的线程中发生了异常,这样A方法是不会发生回滚的。因为两个操作不在一个线程中,获取到的数据库连接不一样,从而是两个不同的事务,所以也不会回滚。

有关Spring事务(四)-事务失效场景的更多相关文章

  1. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  2. ruby-on-rails - 带 Spring 锁的 Rails 4 控制台 - 2

    我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.

  3. ruby - 分布式事务和队列,ruby,erlang,scala - 2

    我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和

  4. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  5. spring.profiles.active和spring.profiles.include的使用及区别说明 - 2

    转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev

  6. ruby - 如何使用 ruby​​ mysql2 执行事务 - 2

    我已经开始使用mysql2gem。我试图弄清楚一些基本的事情——其中之一是如何明确地执行事务(对于批处理操作,比如多个INSERT/UPDATE查询)。在旧的ruby-mysql中,这是我的方法:client=Mysql.real_connect(...)inserts=["INSERTINTO...","UPDATE..WHEREid=..",#etc]client.autocommit(false)inserts.eachdo|ins|beginclient.query(ins)rescue#handleerrorsorabortentirelyendendclient.commi

  7. ruby-on-rails - 在 rails 中提交后回滚事务 - 2

    保存成功后可以回滚吗?让我有一个带有属性名称、电子邮件等的用户模型。例如u=User.newu.name="test_name"u.email="test@email.com"u.save现在记录将成功保存在数据库中,之后我想回滚我的事务(不是销毁或删除)。有什么想法吗? 最佳答案 您可以通过交易来做到这一点,请参阅http://markdaggett.com/blog/2011/12/01/transactions-in-rails/例子:User.transactiondoUser.create(:username=>'Nemu

  8. ruby-on-rails - Spring 不起作用。 [未初始化常量 Spring::SID::DL] - 2

    我无法运行Spring。这是错误日志。myid-no-MacBook-Pro:myid$spring/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/lib/spring/sid.rb:17:in`fiddle_func':uninitializedconstantSpring::SID::DL(NameError)from/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/li

  9. 【云原生】SpringCloud-Spring Boot Starter使用测试 - 2

    目录SpringBootStarter是什么?以前传统的做法使用SpringBootStarter之后starter的理念:starter的实现: 创建SpringBootStarter步骤在idea新建一个starter项目、直接执行下一步即可生成项目。 在xml中加入如下配置文件:创建proterties类来保存配置信息创建业务类:创建AutoConfiguration测试如下:SpringBootStarter是什么? SpringBootStarter是在SpringBoot组件中被提出来的一种概念、简化了很多烦琐的配置、通过引入各种SpringBootStarter包可以快速搭建出一

  10. ruby - 如何使用 PG Ruby Gem 有条件地回滚事务 - 2

    我目前正在上一门数据库类(class),其中一个实验室问题让我困惑于如何实现上述内容,事实上,如果可能的话。我试过搜索docs但是定义的交易方式比较模糊。这是我第一次尝试在没有Rails的情况下进行任何数据库操作,所以我有点迷茫。我已经成功地创建了一个到我的postgresql数据库的连接并且可以执行语句,我需要做的最后一件事是根据一个简单的条件回滚一个事务。请允许我向您展示代码:require'pg'@conn=PG::Connection.open(:dbname=>'db_15_11_labs')@conn.prepare('insert','INSERTINTOhouse(ho

随机推荐