Spring事务管理深度剖析:原理与最佳实践

关注微信公众号 “程序员小胖” 每日技术干货,第一时间送达!

引言

在现代企业级应用开发中,数据的一致性和完整性是系统稳定性的重要保障。随着用户量的增长和业务复杂度的提升,系统不可避免地要面对高并发的挑战。在这样的背景下,事务管理成为了确保数据操作正确性和系统稳定性的关键技术。Spring框架作为Java开发中广泛使用的一站式解决方案,其内置的事务管理功能为开发者提供了强大的支持。

Spring事务实现原理

  1. Spring事务底层是基于数据库事务和AOP机制的
  2. 首先对于使用了@Transactional注解的Bean, Spring会创建一个代理对象作为Bean
  3. 当调用代理对象的方法时, 会先判断该方法上是否加了@Transactional注解
  4. 如果加了,那么则利用事务管理器创建一个数据库连接
  5. 并且修改数据库连接的autocommit属性为false, 禁止此连接的自动提交, 这是实现Spring事务非常重要的一步
  6. 然后执行当前方法, 方法中会执行sql
  7. 执行完当前方法后, 如果没有出现异常就直接提交事务
  8. 如果出现了异常, 并且这个异常是需要回滚的就会回滚事务, 否则仍然提交事务
  9. Spring事务的隔离级别对应的就是数据库的隔离级别
  10. Spring事务的传播机制是Spring事务自己实现的, 也是Spring事务中最复杂的
  11. Spring事务的传播机制是基于数据库连接来做的, 一个数据库连接一个事务, 如果传播机制配置为需要新开一个事务, 那么实际上就是先建立一个数据库连接, 在此新数据库连接上执行sql

Spring传播机制

在调用接下来的方法时, 到底是共用同一个事务呢? 还是新开一个事务呢? 这就是传播机制, 开发人员可以根据不同的业务场景使用与之匹配的配置.

  1. REQUIRED(Spring默认的事务传播类型): 如果当前没有事务, 则自己新建一个事务, 如果当前存在事务, 则加入这个事务
  2. SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
  3. MANDATORY: 当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
  4. REQUIRES_NEW: 创建一个新事务,如果存在当前事务,则挂起该事务。
  5. NOT_SUPPORTED: 以非事务方式执行,如果当前存在事务则挂起当前事务
  6. NEVER: 不使用事务,如果当前事务存在,则抛出异常
  7. NESTED: 如果当前事务存,则在嵌套事务中执行,否则和REQUIRED的操作一样(开启一个事务)

事务实现方式

在Spring框架中,事务管理可以通过两种主要方式实现:声明式事务管理和编程式事务管理。

声明式事务管理

实现原理

声明式事务管理是基于AOP(面向切面编程)实现的。Spring通过AOP为那些需要事务管理的方法创建代理,这样在方法执行前后,Spring可以自动地进行事务的开启、提交或回滚操作。这种方式的事务管理是非侵入式的,因为它不需要修改业务逻辑代码。

使用场景

声明式事务管理最适合于事务边界清晰、事务属性统一的场景。例如,在服务层的方法中,如果每个方法都需要相同的事务属性(如传播行为、隔离级别等),那么使用声明式事务管理可以大大减少重复的事务管理代码。

Java代码示例

@Service
public class AccountService {
    @Autowired
    private AccountRepository accountRepository;

    @Transactional  // 声明式事务注解
    public void transferMoney(String fromAccountId, String toAccountId, BigDecimal amount) {
        Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow(() -> new EntityNotFoundException("Account not found"));
        Account toAccount = accountRepository.findById(toAccountId).orElseThrow(() -> new EntityNotFoundException("Account not found"));

        fromAccount.decreaseBalance(amount);
        toAccount.increaseBalance(amount);

        accountRepository.save(fromAccount);
        accountRepository.save(toAccount);
    }
}

编程式事务管理

实现原理

编程式事务管理允许开发者在代码中显式地管理事务边界。这种方式需要使用PlatformTransactionManager接口或者TransactionTemplate类来编程式地控制事务的开启、提交和回滚。编程式事务管理提供了更细粒度的控制,但也增加了代码的复杂性。

使用场景

编程式事务管理适用于需要进行细粒度事务控制的场合,例如在复杂的业务逻辑中,可能需要根据运行时的情况动态地决定是否开启新事务或者加入到现有事务中。

Java代码示例

@Service
public class TransactionService {
    @Autowired
    private PlatformTransactionManager transactionManager;

    public void createEntity(MyEntity entity) {
        TransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        definition.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
        
        TransactionStatus status = transactionManager.getTransaction(definition);
        try {
            myRepository.save(entity);
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

声明式事务管理因其简单和非侵入性而在大多数情况下被推荐使用。它通过AOP自动处理事务的开启和关闭,使得业务代码更加清晰。而编程式事务管理则提供了更高的灵活性,允许开发者在代码中精确地控制事务的行为,适用于更加复杂的事务场景。开发者应根据具体的业务需求和场景选择最合适的事务管理方式。

事务失效的场景及原因

同样的,如果事务使用不当的话会造成事务失效,导致程序最终的执行结果是不符合预期的。为了避免犯错,今天详细介绍一下事务失效的几种场景及根本原因,并对应的提出解决方案。

  1. 类没有被Spring管理

  2. 数据库引擎不支持事务

  3. 方法内的自调用: Spring事务是基于AOP的,只要使用代理对象调用某个方法时,Spring事务才能生效,而在一个方法中调用使用this.xxx()调用方法时,this并不是代理对象,所以会导致事务失效.

  • 把调用方法拆分到另外一个Bean中
  • 自己注入自己
  • AopContext.currentProxy()+@EnableAspectJAutoProxy(ex poseProxy = true)
  1. 方法是private的: Spring事务会基于CGLIB来进行AOP,而CGLIB会基于父子类来失效,子类是代理类,父类是被代理类,如果父类中的某个方法是private的,那么子类就没有办法重写它,也就没有办法额外增加Spring事务的逻辑.
  2. 方法是final的: 原因和private是一样的,也是由于子类不能重写父类中的final的方法.
  3. 单独的线程调用方法: 当Mybatis或JdbcTemplate执行SQL时,会从ThreadLocal中去获取数据库连接对象,如果开启事务的线程和执行SQL的线程是同一个,那么就能拿到数据库连接对象,如果不是同一个线程,那就拿到不到数据库连接对象,这样,Mybatis或JdbcTemplate就会自己去新建一个数据库连接用来执行SQL,此数据库连接的autocommit为true,那么执行完SQL就会提交,后续再抛异常也就不能再回滚之前已经提交了的SQL了.
  4. 没加@Configuration注解: 如果用SpringBoot基本没有这个问题,但是如果用的Spring,那么可能会有这个问题,这个问题的原因其实也是由于Mybatis或JdbcTemplate会从ThreadLocal中去获取数据库连接,但是ThreadLocal中存储的是一个map,map的key为DataSource对象,value为连接对象,而如果我们没有在AppConfig上添加@Configuration注解的话,会导致MAP中存的DataSource对象和Mybatis和JdbcTemplate中的DataSource对象不相等,从而也拿不到数据库连接,导致自己去创建数据库连接了.
  5. 异常被吃掉: 如果Spring事务没有捕获到异常,那么也就不会回滚了,默认情况下Spring会捕获RuntimeException和Error.

总结

总结来说,掌握Spring事务管理不仅是提升个人技术能力的关键,也是构建企业级应用的重要基石。希望本文能够作为您在实际工作中高效管理并发事务的指南,帮助您在面对高并发挑战时,能够游刃有余,构建出更加可靠和高效的系统。未来的道路上,让我们一起不断探索和学习,为企业级应用开发贡献更多的智慧和力量。

在这里插入图片描述

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值