写点什么

今天一定要搞清楚 Spring 事务

  • 2023-04-25
    湖南
  • 本文字数:4847 字

    阅读完需:约 16 分钟

什么是 Spring 事务?

Spring 事务是指在 Spring 框架中对于数据库操作的一种支持,它通过对一组数据库操作进行整体控制来保证数据的一致性和完整性。Spring 事务可以保证在一组数据库操作执行时,要么所有操作都执行成功,要么所有操作都回滚到之前的状态,从而避免了数据不一致的情况。

Spring 事务实现方式

Spring 事务可以通过编程式事务和声明式事务两种方式来实现。编程式事务需要在代码中手动控制事务的开始、提交和回滚等操作,而声明式事务则是通过在配置文件中声明事务的切入点和通知等信息来自动控制事务的行为。

Spring 编程式事务

Spring 编程式事务需要在代码中获取事务管理器,并通过该事务管理器获取事务对象,然后使用该事务对象来控制事务的开始、提交和回滚等操作。

public void transferMoney(Account fromAccount, Account toAccount, double amount) {    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());    try {        fromAccount.withdraw(amount);        toAccount.deposit(amount);        transactionManager.commit(status);    } catch (Exception e) {        transactionManager.rollback(status);    }}
复制代码

在上面的代码中,首先通过transactionManager.getTransaction方法获取事务对象status,然后在try块中执行转账操作,最后通过transactionManager.commit(status)提交事务。如果在转账操作中发生了异常,则会通过transactionManager.rollback(status)回滚事务。


编程式事务的优点是灵活性高,可以根据具体的业务需求来灵活控制事务的行为。不过缺点是代码冗长,可读性差,而且容易出现错误。

Spring 声明式事务

Spring 声明式事务需要在配置文件中声明事务管理器、事务通知等元素,然后在需要使用事务的方法上添加事务切面的注解即可。

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">    <property name="dataSource" ref="dataSource"/></bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="transferMoney" propagation="REQUIRED"/> </tx:attributes></tx:advice>
<aop:config> <aop:pointcut id="transferPointcut" expression="execution(* com.example.TransferService.transferMoney(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="transferPointcut"/></aop:config>
复制代码

在上面的配置文件中,首先声明了事务管理器transactionManager,然后定义了事务通知txAdvice,该通知会在transferMoney方法执行时进行事务管理。最后通过aop:configaop:advisor来将txAdvice应用于transferPointcut定义的切入点上。


声明式事务的优点是通过配置文件来管理事务,避免了在代码中手动控制事务的繁琐和容易出错的问题。不过缺点是灵活性较差,不能根据具体的业务需求来灵活控制事务的行为。


声明式事务注解方式

Spring 声明式事务也可以通过注解的方式来实现。具体来说,可以在需要使用事务的方法上添加@Transactional注解,并通过该注解的属性来指定事务的传播机制、隔离级别、超时时间等信息。

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 3600)public void transferMoney(Account fromAccount, Account toAccount, double amount) {    fromAccount.withdraw(amount);    toAccount.deposit(amount);}
复制代码

在上面的代码中,通过@Transactional注解来声明了事务的传播机制为Propagation.REQUIRED,隔离级别为Isolation.DEFAULT,超时时间为 3600 秒。在方法执行时,Spring 会根据注解中的信息自动管理事务的行为。


注解式声明事务的优点是代码简洁、可读性好,灵活性和可用性高,容易维护和调试。但是它也有一些缺点,例如注解的使用可能会导致代码分散,而且无法通过配置文件来管理事务,而且可能会引入一些意想不到的问题。

@Transactional注解的常用参数包括:

  • value:该属性可以用来指定事务管理器的名称,如果只有一个事务管理器,则可以省略该属性。

  • propagation:该属性用于指定事务的传播机制,包括REQUIREDSUPPORTSMANDATORYREQUIRES_NEWNOT_SUPPORTEDNEVERNESTED共 7 种。

  • isolation:该属性用于指定事务的隔离级别,包括DEFAULTREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE共 5 种。

  • timeout:该属性用于指定事务的超时时间,单位为秒,默认值为-1,表示没有超时限制。

  • readOnly:该属性用于指定事务是否为只读事务,默认值为 false,表示事务可读可写。

  • rollbackFor:该属性用于指定事务回滚的条件,如果出现指定的异常类型,则事务会回滚。

  • noRollbackFor:该属性用于指定事务不回滚的条件,如果出现指定的异常类型,则事务不会回滚。


除了上述常用参数外,@Transactional注解还有其他一些参数,例如transactionManagerrollbackForClassNamenoRollbackForClassNamevalue等,具体用法可以参考 Spring 官方文档。


在使用@Transactional注解时,需要根据具体的业务需求来选择合适的参数,以保证事务的正确性和可靠性。

事务注解失效情况

Spring 的声明式事务注解可能失效的情况包括:

  • 注解被错误地放置在了类上而不是方法上。

  • 方法是 private 或 final 的,而且它们不能从外部调用。

  • 方法没有被公开暴露,也就是说,它们不是公开的方法(public)。

  • 被注解的方法是静态的。

  • 被注解的方法依赖于其他未被注解的方法(例如,被注解的方法调用了一个未被注解的私有方法)。

  • 被注解的方法在另一个类中被调用。

  • 注解被错误地配置了,例如使用了错误的名称或参数。

  • 类没有被正确地扫描,因此 Spring 无法找到应该被注解的方法。

  • 注解属性 propagation 设置错误

  • 注解属性 rollbackFor 设置错误

  • 异常被 catch 捕获导致 @Transactional 失效

  • 数据库引擎不支持事务

  • 多个切面的影响也会导致事务失效


如果遇到声明式事务注解失效的情况,需要检查上述问题并进行相应的修复。


代码规范中强调 @Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的敌方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎、消息补偿、统计修正等。


在使用 Spring 事务时,需要注意对于事务的传播机制和隔离级别的设置,以及对于事务的异常处理等问题。正确地使用 Spring 事务可以提高系统的数据一致性和可靠性。

什么是 Spring 事务传播机制

Spring 事务传播机制是指在多个事务操作发生时,如何管理这些操作之间的事务关系。Spring 事务传播机制可以通过Propagation枚举类中的不同值来指定,共包括七种不同的传播行为。具体来说,Spring 事务传播机制包括以下七种:

  • REQUIRED:如果当前没有事务,则创建一个新的事务;如果当前已经存在事务,则加入该事务。这是默认的传播行为。

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

  • MANDATORY:必须在一个已存在的事务中执行,否则就抛出TransactionRequiredException异常。

  • REQUIRES_NEW:创建一个新的事务,并在该事务中执行;如果当前存在事务,则将当前事务挂起。

  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。

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

  • NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新的事务。


在使用 Spring 事务传播机制时,需要根据具体的业务需求来选择合适的传播行为,以保证事务的正确性和可靠性。同时,需要注意事务的异常处理等问题,以避免数据不一致或丢失的情况发生。

@Transactional注解中的传播机制

除了在代码中使用编程式事务和声明式事务外,Spring 还提供了@Transactional注解来实现事务控制。在使用@Transactional注解时,可以通过propagation属性来指定事务的传播机制,例如:

@Transactional(propagation = Propagation.REQUIRED)public void transferMoney(Account fromAccount, Account toAccount, double amount) {    fromAccount.withdraw(amount);    toAccount.deposit(amount);}
复制代码

在上面的代码中,通过propagation属性来指定了事务的传播机制为Propagation.REQUIRED,即如果当前没有事务,则创建一个新的事务;如果当前已经存在事务,则加入该事务。


除了Propagation枚举类中的七种传播行为外,@Transactional注解还可以通过isolationtimeoutreadOnly等属性来指定事务的隔离级别、超时时间和只读事务等信息。


在使用@Transactional注解时,需要根据具体的业务需求来选择合适的传播行为和其他属性,以保证事务的正确性和可靠性。

Spring 事务的隔离级别

Spring 事务的隔离级别是指多个事务之间的隔离程度,它可以通过设置isolation属性来指定。Spring 事务的隔离级别包括以下五种:

  • DEFAULT:默认的隔离级别,由底层数据库引擎决定。

  • READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据。该级别会导致“脏读”、“不可重复读”和“幻读”等问题。

  • READ_COMMITTED:只允许读取已经提交的数据。该级别可以避免“脏读”,但可能会导致“不可重复读”和“幻读”等问题。

  • REPEATABLE_READ:保证在同一个事务中多次读取同一数据时,该数据的值不会发生变化。该级别可以避免“脏读”和“不可重复读”,但可能会导致“幻读”等问题。

  • SERIALIZABLE:最高的隔离级别,强制事务串行执行,避免了“脏读”、“不可重复读”和“幻读”等问题。但是该级别会对性能产生较大的影响,因此一般不建议使用。


在选择隔离级别时,需要根据具体的业务需求来选择合适的级别。一般来说,如果不需要在事务中读取未提交的数据,那么可以选择READ_COMMITTED级别;如果需要避免“不可重复读”问题,可以选择REPEATABLE_READ级别;如果需要避免“幻读”问题,可以选择SERIALIZABLE级别。但是需要注意的是,隔离级别越高,事务的并发性越差,因此需要根据具体业务场景来权衡隔离级别和性能。

不可重复读和幻读的区别

不可重复读和幻读都是在并发读写数据时可能出现的问题。


不可重复读指的是在一个事务中多次读取同一数据,但是由于其他事务的修改,导致两次读取的结果不一致。例如,事务 A 在读取某个数据时,事务 B 修改了该数据并提交,然后事务 A 再次读取该数据时,得到了不同的结果。


幻读是指在一个事务中多次读取同一范围内的数据,但是由于其他事务的插入操作,导致两次读取的结果不一致。例如,事务 A 在读取某个范围内的数据时,事务 B 插入了一条数据并提交,然后事务 A 再次读取该范围内的数据时,得到了不同的结果。


不可重复读和幻读的区别在于,不可重复读是在读取同一数据时出现问题,而幻读是在读取同一范围内的数据时出现问题。解决不可重复读的问题可以使用锁机制或者提高事务的隔离级别,而解决幻读的问题可以使用锁机制或者使用更高的隔离级别,例如SERIALIZABLE级别。

rollbackFor属性

在使用@Transactional注解进行声明式事务管理时,可以通过rollbackFor属性来指定哪些异常类型需要回滚事务。需要注意的是,rollbackFor属性指定的异常类型必须是Throwable类型或其子类,并且必须包含在Spring事务抛出的异常类型之内。如果指定的异常类型不正确或不在事务回滚的异常类型之内,可能会导致事务无法正确回滚或者回滚不完整的问题。


如果需要指定多个异常类型,可以使用数组的方式进行指定,例如:

@Transactional(rollbackFor = {SQLException.class, IOException.class})public void doSomething() {    // ...}
复制代码

在上面的代码中,指定了SQLExceptionIOException两种异常类型需要回滚事务。默认情况只有 RuntimeException 和 Error 会触发事务回滚。


除了rollbackFor属性外,@Transactional注解还有其他一些属性可以用于指定事务的属性和行为,包括propagationisolationtimeoutreadOnly等。需要根据具体的业务需求来选择合适的属性和行为,以保证事务的正确性和可靠性。


作者:雷哥不爱写代码

链接:https://juejin.cn/post/7225132351448907832

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
今天一定要搞清楚Spring事务_Java_做梦都在改BUG_InfoQ写作社区