版权声明:本文为 小异常 原创文章,非商用自由转载-保持署名-注明出处,谢谢!
本文网址:https://blog.csdn.net/sun8112133/article/details/80757848
通过上一篇的讲解,相信大家已经对事务有了一个简单的认识,本篇就带大家走进 Spring中的事务管理,来看看在 Spring中对事务如何进行管理。
本篇只对 Spring事务管理 作介绍,不进行相关配置,后续博客中会有专门讲解。
所谓编程式事务指的是通过编码方式实现事务,即类似于 JDBC编程 实现事务管理。因为编程式事务管理在实际开发中基本不使用,所以通过以下例子了解即可。
本实际是在 MySQL中 操作事务的小例子,此例子即是编程式事务管理。假如 ABCD 一个事务中的四个动作,那么实现事务的简要代码如下:
Connection conn = null;
try{
// 1 获得连接
conn = ...;
// 2 开启事务
conn.setAutoCommit(false);
A
B
C
D
// 3 提交事务
conn.commit();
} catch(...) {
// 4 回滚事务
conn.rollback();
}
我们还可以在事务中设置保存点,假如有这样一种情况,一个事务中有ABCD四个动作,AB必须成功才可以完成,而CD如果失败也不会影响事务。也就是:AB是必须的,CD是可选的。
Connection conn = null;
Savepoint savepoint = null;
// 保存点,记录操作的当前位置,之后可以回滚到指定的位置。(可以回滚一部分)
try{
// 1 获得连接
conn = ...;
// 2 开启事务
conn.setAutoCommit(false);
A
B
savepoint = conn.setSavepoint();
C
D
// 3 提交事务
conn.commit();
} catch(...) {
if (savepoint != null) { // CD异常
// 回滚到CD之前
conn.rollback(savepoint);
// 提交AB
conn.commit();
} else { // AB异常
// 回滚AB
conn.rollback();
}
}
声明式事务管理是建立在 AOP 之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
在 spring中,只有在声明式事务管理中可以通过设置五个属性来管理事务。
事务的隔离级别意在解决事务在并发操作时出现的问题,当然每解决一个问题时,都会影响到数据库的性能,所以它提供了四个隔离级别来供程序员选择,程序员可以根据自己所做的项目选择设置合适的隔离级别。
在 Spring中 有五个事务隔离级别的常量,如下表:
隔离级别 | 说明 |
---|---|
DEFAULT | 使用底层数据库的默认隔离级别,对于大多数数据库来说,默认隔离级别都是 READ_UNCOMMITTED |
READ_UNCOMMITTED | 允许事务读取未被其他事务提交的变更。脏读、不可重复读和幻读的问题都会出现 |
READ_COMMITED | 只允许事务读取已经被其它事务提交的变更。可以避免脏读,但不可重复读和幻读问题仍然可能出现 |
REPEATABLE_READ | 确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题仍然存在 |
SERIALIZABLE | 确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入,更新和删除操作。所有并发问题都可以避免,但性能十分低下 |
事务的隔离级别要得到底层数据库引擎的支持,而不是应用程序或者框架的支持。
Oracle 支持2种事务隔离级别:READ_COMMITTED、SERIALIZABLE
MySQL 支持4种事务隔离级别。
值得一提的是:大多数数据库默认的事务隔离级别是 read committed,比如 SQL Server , Oracle。但 MySQL 的默认隔离级别是 repeatable read。
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。最低级别,它存在3个问题(脏读、不可重复读、幻读)。
读已提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。 它解决了脏读问题,存在2个问题(不可重复读、幻读)。
可重复读,就是在开始读取数据(事务开启)时,不再允许修改操作 。它解决了脏读和不可重复读,还存在1个问题(幻读)。
序列化,或串行化。就是将每个事务按一定的顺序去执行,它将隔离问题全部解决,但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
事务的传播行为是 Spring框架 中独有的事务增强特性,他不属于数据库行为。使用事务传播行为可以为我们的开发工作提供许多便利。我们一般将事务写在 service层中,可能有些小伙伴听说过 “service层的事务方法最好不要嵌套” 的传说,接下来我们就走近看看事务到底有哪些传播行为?
事务的传播行为说的专业点就是用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法时的事务如何传播。
用一句话概括事务的传播行为就是 大事务中嵌套了小事务,大事务传播给小事务。
在 spring 中共有七种事务的传播行为,见下表:
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
关于这七种事务的传播行为细节将在后续博客中进行讲解,我们一般最常用使用的是 REQUIRES_NEW 传播行为,接下来我们就以转账的例子来看看它究竟有什么作用。
有这样一个需求:用户A 要一键转给 用户B 和 用户C 100元,如果 转给 用户C 的时候转账失败了,那也不要影响到转给 用户B 的钱。
根据这样的需求我们可以知道,用户A 转给 用户B 是一次转账(小事务),用户A 转给 用户C 也是一次转账(小事务)。用户A 转给 用户B 和 用户C 是一次一键转账(大事务),如下图:
通过上图我们看出,它是由一个大事务包着两个小事务,一般情况下如果任意一个小事务中发生了异常势必会影响到其他小事务(回滚到大事务的最初状态),这显然不是我们想要的效果。我们可以通过事务的 REQUIRES_NEW 传播行为将大事务的行为传播到小事务中,也就是如果任意一个小事务发生了异常,也不会影响到其他小事务(只回滚当前小事务),如下图:
Spring事务只读属性,是指对事务性资源进行只读操作或者是可读写操作。所谓事务性资源就是指那些被事务管理的资源,如数据源、JMS 资源,以及自定义的事务性资源等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。
Spring事务超时时间,是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。
Spring事务管理中也对回滚进行了规则限定,在通常情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务。如果没有抛出任何异常,或者抛出了已检查异常,则正常提交事务。我们可以根据需要人为控制事务在抛出某些未检查异常时仍然提交事务,或者在抛出某些已检查异常时回滚事务。