当今软件开发行业中,事务管理是一个不可或缺的重要组成部分。随着企业应用的复杂性和数据交互的增加,确保数据的一致性和完整性变得越来越关键。Spring框架作为一种全功能的应用程序开发框架,为我们提供了强大而灵活的事务管理功能。在这篇文章中,我们将深入探讨Spring框架的事务管理机制,揭示其工作原理和优势。无论您是初学者还是有经验的开发人员,本文将帮助您更好地理解和利用Spring事务管理,以提升应用程序的可靠性和性能。
目录
之前我们在MySQL的文章中就有介绍过什么是事务 :【MySQL数据库 | 第十五篇】事务_数据库提交事务-CSDN博客
简单的来说:事务是一组操作的集合,他是一个不可分割的工作单位,这些操作要么同时成功,妖魔同时失败
我们举一个简单的例子:银行转账
我们可以简单的认为一共分为这两步操作:
1.给A转账1000元2.B账户扣除1000元
这两个操作放到数据库中就是两条很简单的语句,但是这两个语句可以分开执行嘛?
答案是不能,因为如果分开执行,先执行1,再执行2,如果B的账户根本没有1000元,那么由于先执行了1,A的账户也会收款1000元,这将是不必要的麻烦
因此我们提出了事务,把1,2操作同时执行,如果2操作不可以成功执行,1操作也不会执行,我们就说我们把1和2操作捆绑成为了一个事务。
在Spring框架中,事务是指一组数据库操作(例如插入、更新、删除等)被视为一个不可分割的工作单元,要么全部执行成功,要么全部回滚,以确保数据的一致性和完整性。
Spring的事务管理能力是通过AOP(面向切面编程)和代理技术实现的。它提供了一个事务抽象层,使开发人员可以在应用程序中轻松地实现事务功能,而无需过多关注底层事务管理的细节。
Spring中的事务可用于将需要在事务范围内执行的一组数据库操作绑定到一个事务中。通过将这些操作纳入一个事务中,我们可以确保这些操作要么全部成功提交,要么全部回滚,以保持数据的一致性。
Spring框架提供了多种事务管理的策略,包括声明式事务管理和编程式事务管理。声明式事务管理可以通过@Transactional注解或XML配置来实现,可以将事务的定义与业务逻辑代码分离。而编程式事务管理则允许开发人员在代码中显式地控制事务的开始、提交和回滚。
通过Spring框架的事务管理特性,开发人员可以更加方便地管理和控制数据库操作的事务性,并确保数据的一致性和完整性。同时,Spring框架在事务管理方面还提供了灵活的配置选项,以适应不同的业务场景和需求。
事务的基本操作通常包括以下几个步骤:
开启事务(Begin Transaction):事务的第一步是要开启一个事务,这样后续的所有数据库操作都可以在同一个事务中执行。在Spring中,可以使用@Transactional注解或编程式的方式来开启事务。
执行数据库操作:在事务范围内执行需要进行的数据库操作,包括插入、更新、删除等。这些操作将被纳入事务的管理之下,并在提交或回滚时一起操作。
提交事务(Commit):当所有的数据库操作成功执行并且不出现异常时,事务可以进行提交。提交时,所有的数据库操作将被正式应用到数据库中,使其生效。在Spring中,事务提交通常是由事务管理器自动完成的。
回滚事务(Rollback):如果在事务执行期间出现了异常或错误,事务可以选择回滚,即撤销之前的所有数据库操作。这样可以确保数据的一致性和完整性。在Spring中,可以通过捕获异常或进行手动回滚的方式来触发事务的回滚操作。
需要注意的是,事务的范围通常是由方法的边界决定的。即在开启事务前,进入方法执行,执行数据库操作,根据结果决定是提交还是回滚,然后结束事务。整个过程中,事务贯穿始终。
通过以上基本操作,可以实现数据库操作的原子性、一致性、隔离性和持久性(ACID 特性),以确保数据的正确性和可靠性。Spring框架提供了便捷的事务管理机制,使开发人员能够轻松地实现和控制事务的操作。
如图所示是一个删除指定id部门的操作,而删除了指定id的部门,自然也要删除掉指定id部门的员员工,但是如果在删除完指定id的部门之后,服务器出错抛出了异常,中断了这个操作,那么此时就只删除了部门而没有删除对应部门的员工,这将会在数据库中留下异常的数据,因此我们就需要把这个操作捆绑成为一个事务:删除指定ID的部门和删除对应的员工必须同时执行。
也就是说对串代码进行这种事务模式的修改:
而正如我们前面所介绍的,由于这些操作都是固定的,因此Spring为我们提供了一个注解@Transactional
@Transactional是Spring框架中用于声明式事务管理的注解。通过在方法或类级别上添加@Transactional注解,可以将方法或类纳入事务管理的范围中,使其具备事务特性。
使用@Transactional注解的好处是,它能够将事务的定义与业务逻辑代码分离,提供了一种声明式的方式来配置和管理事务。开发人员可以通过简单地在方法或类上添加@Transactional注解,而无需编写大量的事务管理代码。
@Transactional注解的常用属性:
propagation:指定事务的传播行为。它定义了在方法调用链中嵌套的情况下,事务是如何传播的。常见的传播行为包括REQUIRED(默认值)、REQUIRES_NEW、NESTED等。
isolation:指定事务的隔离级别。它决定了一个事务对于并发修改的数据可以见到多少。常见的隔离级别包括READ_COMMITTED(默认值)、READ_UNCOMMITTED、REPEATABLE_READ、SERIALIZABLE等。
timeout:指定事务的超时时间。如果事务在指定的超时时间内没有完成,将触发事务的回滚。单位为秒,默认值为-1,表示不设定超时。
readOnly:指定事务是否为只读事务。如果事务只涉及读取数据,并不会进行数据修改操作,可以将readOnly属性设置为true,以优化性能。
rollbackFor和noRollbackFor:用于指定特定的异常类型,当这些异常发生时,事务将回滚或不回滚。
除了以上属性,@Transactional注解还可以在XML配置中进行更细粒度的配置,包括指定基于方法名的事务策略、设置事务管理器、指定事务切面等。
总而言之,@Transactional注解提供了一种方便且简洁的方式来实现声明式事务管理。它使得开发人员可以更加专注于业务逻辑,而无需过多关注底层事务管理的细节。同时,通过灵活的属性配置,可以满足不同场景下的事务需求。
在Spring事务管理中,提供了几种事务隔离级别供选择:
READ_COMMITTED(已提交读):在READ_COMMITTED隔离级别下,事务只能读取到已经提交的数据。它可以避免脏读(Dirty Read),但是可能会发生不可重复读(Non-repeatable Read)和幻读(Phantom Read)。
READ_UNCOMMITTED(未提交读):在READ_UNCOMMITTED隔离级别下,事务可以读取到未提交的数据,可能会发生脏读,也就是读取到未提交的、其它事务正在修改的数据。这个隔离级别下,对数据的一致性要求最低。
REPEATABLE_READ(可重复读):在REPEATABLE_READ隔离级别下,事务在执行期间多次读取相同的数据时,保证能看到一致的数据快照。即使在事务期间有其他事务对数据进行了修改,也不会读到这些修改过的数据。但是,可能会发生幻读问题,即一个事务范围内,两次查询同一个条件返回的结果集不一致。为了避免幻读问题,需要使用锁机制或者更高级别的隔离级别。
SERIALIZABLE(串行化):在SERIALIZABLE隔离级别下,事务会按照串行执行的方式进行,每个事务只能看到其它事务已经提交的数据。它提供了最高的隔离级别,避免了脏读、不可重复读和幻读的问题,但是也会带来最高的并发度降低。
需要根据业务场景和需求选择合适的事务隔离级别。默认情况下,Spring事务将使用数据库的默认隔离级别(通常是READ_COMMITTED),可以通过@Transactional注解的isolation属性来显式指定隔离级别。
值得注意的是,不同的数据库可能对事务隔离级别的支持有所不同,特别是在分布式事务环境中,需要考虑数据库的兼容性和一致性要求。
回到我们之前的代码中:
我们此时手动的给这段代码的执行添加一个异常情况:除零算数异常
我们在前端尝试删除后,可以在控制台看到如下信息:
这也就意味着我们成功的把这个方法变为了一个事务
在默认情况下,只有出现RuntimeException才回滚异常。而我们前面的rollbackFor和noRollbackFor用于指定特定的异常类型,当这些异常发生时,事务将回滚或不回滚。
RuntimeException
是 Java 中的一个类,它是Exception
类的子类。RuntimeException
是一个非受检异常(Unchecked Exception),在编码和异常处理中具有特殊的地位。
RuntimeException
可以被分为两种类型:
编程错误引起的异常:这些异常通常是由程序员的错误或代码逻辑错误引起的,例如
NullPointerExeption
(空指针异常)、ArrayIndexOutOfBoundsException
(数组越界异常)、ClassCastException
(类转换异常)等。这些异常发生时通常暗示代码需要进行修复或改进。运行环境异常:这些异常通常是由于程序运行环境的问题引起的,例如
ArithmeticException
(算术异常,如除零异常)或OutOfMemoryError
(内存溢出错误)。这些异常通常无法通过代码修复,需要进行环境的调整或处理。
RuntimeException
及其子类异常通常不需要在方法声明的 throws 子句中进行声明或捕获处理,也就是说,它们是非受检异常。编译器在编译时不会强制要求处理这些异常,编写代码时可以选择捕获或忽略它们。值得注意的是,尽管
RuntimeException
是一个非受检异常,但是在编码中应该尽量避免不必要的RuntimeException
异常的发生,特别是那些可以通过更严谨的代码或输入验证来预防的异常。在实际开发中,合理的异常处理和错误处理将提高代码质量和稳定性。不属于
RuntimeException
异常的异常种类:
Checked Exception(受检异常):这些异常都是
Exception
的子类,但不是RuntimeException
的子类。受检异常在方法的声明中需要显式地进行捕获或在方法签名的throws
子句中进行声明。这些异常通常表示外部因素或错误,例如IOException
(输入输出异常)、SQLException
(数据库异常)等。Error:
Error
是Throwable
的子类,与RuntimeException
并列,它表示 Java 运行时环境中的严重错误。Error
通常是指无法恢复或无法处理的错误,例如OutOfMemoryError
(内存溢出错误)、StackOverflowError
(栈溢出错误)等。与RuntimeException
不同,Error
类型的异常通常不需要捕获或处理,因为它们表示严重的系统问题,需要通过重新启动应用程序或修复环境来解决。
而进行如下修改后,就表示所有的异常都会回滚。
REQUIRED(默认):如果当前存在事务,则加入该事务,如果不存在事务,则创建一个新的事务。
REQUIRES_NEW:无论当前是否存在事务,都会创建一个新的事务。如果当前存在事务,则将其挂起。
SUPPORTS:如果当前存在事务,则加入该事务;如果不存在事务,则以非事务方式执行。
MANDATORY:如果当前存在事务,则加入该事务;如果不存在事务,则抛出异常。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。
NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
NESTED:如果当前存在事务,则在一个嵌套的事务中执行;如果当前不存在事务,则创建一个新的事务。
最常见的是1和2,我们用一个例子来简单说明一下这两个:
首先,我们假设有一个UserService类,其中包含两个方法:methodA()
和methodB()
。假设这两个方法都被声明为带有@Transactional
注解的事务方法。
- @Service
- @Transactional
- public class UserService {
-
- @Autowired
- private UserRepository userRepository;
-
- public void methodA() {
- // 执行一些数据库操作
- }
-
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void methodB() {
- // 执行一些数据库操作
- }
- }
在这个例子中,methodA()
使用了默认的事务传播行为REQUIRED
,而methodB()
使用了传播行为REQUIRES_NEW
。
现在,假设我们在一个服务类中调用这两个方法:
- @Service
- public class TransactionDemoService {
-
- @Autowired
- private UserService userService;
-
- @Transactional
- public void performTransactionDemo() {
- userService.methodA();
- userService.methodB();
- }
- }
在这个示例中,performTransactionDemo()
方法也被声明为带有@Transactional
注解的事务方法。
现在,当我们调用performTransactionDemo()
方法时,以下是事务的行为:
userService.methodA()
方法被调用时,如果当前存在事务,则methodA()
将加入该事务;如果不存在事务,则将创建一个新的事务。
userService.methodB()
方法被调用时,无论当前是否存在事务,methodB()
都将创建一个新的事务。如果当前存在事务,则将其挂起。
通过这个示例,我们可以看到REQUIRED
和REQUIRES_NEW
事务传播行为的区别。REQUIRED
会共享同一个事务,而REQUIRES_NEW
会创建一个独立的新事务,与外部事务无关。
反映到这个代码中,就是A会添加到
performTransactionDemo
事务中,如果performTransactionDemo()
事务回滚,那么A也会回滚,因为两个共用一个事务,而B是新建一个事务,他和performTransactionDemo()
并不在同一个事务里面,并不会随着performTransactionDemo()
事务的回滚而回滚。
当涉及到数据操作时,事务管理是非常重要的。Spring框架提供了强大而灵活的事务管理功能,使开发人员能够轻松地在应用程序中实现数据一致性和可靠性。
本文介绍了Spring框架中常见的事务管理概念和技术,包括事务传播行为。
事务是一组数据库操作的逻辑单元,要么全部成功提交,要么全部回滚。它确保数据的一致性和可靠性。
Spring中的事务管理依赖于底层的事务管理器,例如JDBC事务管理器或JTA事务管理器。这样,我们可以灵活地选择适合应用程序需求的事务管理器。
Spring提供了两种配置事务的方式:基于注解的配置和基于XML的配置。基于注解的配置更为常用和简洁,通过在方法上添加@Transactional
注解来声明事务属性。
在Spring中,事务传播行为决定了事务如何在方法调用之间传播和管理。常见的传播行为包括REQUIRED
、REQUIRES_NEW
、SUPPORTS
、MANDATORY
、NOT_SUPPORTED
、NEVER
和NESTED
。
总而言之,Spring事务管理提供了一种简单且强大的方式来管理数据库事务。通过有效地利用事务传播行为,我们可以确保在应用程序中的多个方法调用或数据库操作之间正确地管理和控制事务。这使得我们能够实现数据的一致性和可靠性,同时保持代码的简洁和可维护性。
Spring的事务管理功能是众多企业级应用程序中不可或缺的一部分,对于构建稳健和可靠的应用程序是至关重要的。希望本文能够帮助读者理解并正确使用Spring中的事务管理机制。
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!