Spring 事务失效的八种场景

这篇具有很好参考价值的文章主要介绍了Spring 事务失效的八种场景。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 抛出检查异常导致事务不能正确回滚

@Service
public class Service1 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            new FileInputStream("aaa");
            accountMapper.update(to, amount);
        }
    }
}
  • 原因:Spring 默认只会回滚非检查异常
  • 解法:配置 rollbackFor 属性

@Transactional(rollbackFor = Exception.class)

2. 业务方法内自己 try-catch 异常导致事务不能正确回滚

@Service
public class Service2 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount)  {
        try {
            int fromBalance = accountMapper.findBalanceBy(from);
            if (fromBalance - amount >= 0) {
                accountMapper.update(from, -1 * amount);
                new FileInputStream("aaa");
                accountMapper.update(to, amount);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  • 原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
  • 解法1:异常原样抛出:

在 catch 块添加 throw new RuntimeException(e);

解法2:手动设置 TransactionStatus.setRollbackOnly()

在 catch 块添加
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

3. aop 切面顺序导致导致事务不能正确回滚

@Service
public class Service3 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            new FileInputStream("aaa");
            accountMapper.update(to, amount);
        }
    }
}
@Aspect
public class MyAspect {
    @Around("execution(* transfer(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        LoggerUtils.get().debug("log:{}", pjp.getTarget());
        try {
            return pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • 原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常…

  • 解法1、2:同情况2 中的解法:1、2

  • 解法3:调整切面顺序,在 MyAspect 上添加 @Order(Ordered.LOWEST_PRECEDENCE - 1) (不推荐)

4. 非 public 方法导致的事务失效

@Service
public class Service4 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional
    void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }
}
  • 原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的

  • 解法1:改为 public 方法

  • 解法2:添加 bean 配置如下(不推荐)

@Bean
public TransactionAttributeSource transactionAttributeSource() {
    return new AnnotationTransactionAttributeSource(false);
}

5. 父子容器导致的事务失效

@Service
public class Service5 {

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }
}

控制器类:

@Controller
public class AccountController {

    @Autowired
    public Service5 service;

    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        service.transfer(from, to, amount);
    }
}

App 配置类

@Configuration
@ComponentScan("day04.tx.app.service")
@EnableTransactionManagement
// ...
public class AppConfig {
    // ... 有事务相关配置
}

Web 配置类

@Configuration
@ComponentScan("day04.tx.app")
// ...
public class WebConfig {
    // ... 无事务配置
}

现在配置了父子容器,WebConfig 对应子容器,AppConfig 对应父容器,发现事务依然失效

  • 原因:子容器扫描范围过大,把未加事务配置的 service 扫描进来

  • 解法1:各扫描各的,不要图简便

  • 解法2:不要用父子容器,所有 bean 放在同一容器

6. 调用本类方法导致传播行为失效

@Service
public class Service6 {

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
        bar();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}
  • 原因:本类方法调用不经过代理,因此无法增强
  • 解法1:依赖注入自己(代理)来调用
  • 解法2:通过 AopContext 拿到代理对象,来调用
  • 解法3:通过 CTW,LTW 实现功能增强

解法1:

@Service
public class Service6 {

	@Autowired
	private Service6 proxy; // 本质上是一种循环依赖

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
		System.out.println(proxy.getClass());
		proxy.bar();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}

解法2:还需要在 AppConfig 上添加 @EnableAspectJAutoProxy(exposeProxy = true)

@Service
public class Service6 {
    
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
        ((Service6) AopContext.currentProxy()).bar();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}

7. @Transactional 没有保证原子行为

@Service
public class Service7 {

    private static final Logger logger = LoggerFactory.getLogger(Service7.class);

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) {
        int fromBalance = accountMapper.findBalanceBy(from);
        logger.debug("更新前查询余额为: {}", fromBalance);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }

    public int findBalance(int accountNo) {
        return accountMapper.findBalanceBy(accountNo);
    }
}

上面的代码实际上是有 bug 的,假设 from 余额为 1000,两个线程都来转账 1000,可能会出现扣减为负数的情况

  • 原因:事务的原子性仅涵盖 insert、update、delete、select … for update 语句,select 方法并不阻塞
    Spring 事务失效的八种场景,spring,java
    如上图所示,红色线程和蓝色线程的查询都发生在扣减之前,都以为自己有足够的余额做扣减

8. @Transactional 方法导致的 synchronized 失效

针对上面的问题,能否在方法上加 synchronized 锁来解决呢?

@Service
public class Service7 {

    private static final Logger logger = LoggerFactory.getLogger(Service7.class);

    @Autowired
    private AccountMapper accountMapper;

    @Transactional(rollbackFor = Exception.class)
    public synchronized void transfer(int from, int to, int amount) {
        int fromBalance = accountMapper.findBalanceBy(from);
        logger.debug("更新前查询余额为: {}", fromBalance);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }

    public int findBalance(int accountNo) {
        return accountMapper.findBalanceBy(accountNo);
    }
}

答案是不行,原因如下:

  • synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有 commit 等操作,它们并未处于 sync 块内

  • 可以参考下图发现,蓝色线程的查询只要在红色线程提交之前执行,那么依然会查询到有 1000 足够余额来转账
    Spring 事务失效的八种场景,spring,java

  • 解法1:synchronized 范围应扩大至代理方法调用

  • 解法2:使用 select … for update 替换 select

Spring为什么会失效?

这是因为 Spring事务是由 AOP机制实现的,AOP机制的本质就是动态代理,更直白的说:从 Spring IOC容器获取 bean时,Spring会为目标类创建代理,从而支持事务的。

事务需要的 Advisor等资源是在 Spring创建代理类时去创建的,因此,注释 ServiceImpl类的 @Service注解,该类就不受 Spring容器管理,那么事务需要的 Advisor资源就无法生成,事务自然就失效了。文章来源地址https://www.toymoban.com/news/detail-633103.html

到了这里,关于Spring 事务失效的八种场景的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring事务失效场景之类内部方法调用及解决方案

    在日常开发中,经常有需要使用事务来保证数据一致性的情况。简单点的话直接在方法上面加@Transactional注解就可以了。 但这样存在一个问题,在整个业务方法层面加注解会把很多并不需要归入事务的操作也归入到了事务里面,这样会可能会出现大事务的情况,影响系统性能

    2024年01月17日
    浏览(48)
  • 面试官让列举Spring的事务会失效的场景,我说了8个

    本文分享自华为云社区《哪些场景下Spring的事务会失效?》,作者:冰 河 。 在日常工作中,如果对Spring的事务管理功能使用不当,则会造成Spring事务不生效的问题。而针对Spring事务不生效的问题,也是在跳槽面试中被问的比较频繁的一个问题。 今天,我们就一起梳理下有哪

    2024年02月11日
    浏览(40)
  • STM32 GPIO的八种工作模式各有特点,适用于不同的应用场景

    学了挺久的单片机老是记不住每种模式的运用场景今天用通义千问总结了一下作为鞭策顺便记录一下 STM32 GPIO的八种工作模式各有特点,适用于不同的应用场景。以下是每种模式的简要描述及其对应的应用场景: 1. **GPIO_Mode_AIN** - **模拟输入**    - **应用场景**: 当GPIO引脚作为

    2024年04月11日
    浏览(86)
  • Spring事务失效,同一个类中的内部方法调用事务失效

    所谓声明式事务,也就是通过配置的方式,比如通过 配置文件xml或者注解 的方式,来告诉Spring哪些方法需要Spring帮忙管理事务,然后开发者只需要关注业务代码,而事务的事情则由Spring自动帮我们控制。 配置文件的方式 :即在spring.xml文件中进行统一配置,开发者基本不用

    2023年04月18日
    浏览(47)
  • spring动态代理失效,AOP失效,事务@Transactional失效原因

    事务基于@Transactional注解和AOP(动态代理) 对于基于接口动态代理的 AOP 事务增强来说,由于接口的方法都必然是 public 的,这就要求实现类的实现方法也必须是 public 的(不能是 protected、private 等),同时不能使用 static 的修饰符。所以,可以实施接口动态代理的方法只能是

    2024年02月15日
    浏览(50)
  • Spring的事务 方法A调用方法B,事务是否失效

    Springboot开启了事务的方法调用没有事务的方法: 提示:上方标题是一个很笼统的场景,详情展开如下,先说结论: 总结: 方法A调用方法B: 场景一 如果A和B方法在同一个类中: 如果A加@Transactional注解,B加不加@Transactional注解,事务是有效的,则AB在同一事务中。 如果A不加

    2024年01月17日
    浏览(58)
  • 遇到Spring事务失效,你该怎么办?

    Spring 事务场景失效是一个常见的问题。今天来分析这个问题。 失效原因 事务方法被final、static修饰:这是因为Spring事务的实现依赖于AOP技术,而final、static方法无法被代理,因此在这些方法中调用事务方法,事务无法生效。 方法访问权限不是public:Spring事务的实现也

    2023年04月15日
    浏览(49)
  • Java List集合取交集的八种不同实现方式

    码到三十五 : 个人主页 心中有诗画,指尖舞代码,目光览世界,步履越千山,人间尽值得 ! 在Java中,取两个List集合的交集可以通过多种方式实现,包括使用Java 8的Stream API、传统的for循环遍历、使用集合的retainAll方法,以及使用Apache Commons Collections库等。 方法一:使用Jav

    2024年04月12日
    浏览(37)
  • spring中事务失效的情况(常见的5种)

    1.多线程调用 从上面的例子中,我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。 这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,ad

    2024年02月13日
    浏览(36)
  • spring事物失效场景

    在使用SpringBoot的开发过程中,我们有时候会遇到明明加了事务但是却不生效的场景,今天就稍微整理一下。 在Spring Boot中,事务是基于注解或XML配置的方式进行声明的。如果一个方法没有被声明为事务,那么其中的数据库操作将不会受到事务管理的影响,可能导致数据不一致

    2024年02月08日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包