Spring 事务一个被讹传很广说法是:一个事务方法不应该调用另一个事务方法,否则将产生两个事务。结果造成开发人员在设计事务方法时束手束脚,生怕一不小心就踩到地雷。
其实这种是不认识 Spring 事务传播机制而造成的误解,Spring 对事务控制的支持统一在 TransactionDefinition 类中描述,该类有以下几个重要的接口方法:
- int getPropagationBehavior():事务的传播行为
- int getIsolationLevel():事务的隔离级别
- int getTimeout():事务的过期时间
- boolean isReadOnly():事务的读写特性。
很明显,除了事务的传播行为外,事务的其它特性 Spring 是借助底层资源的功能来完成的,Spring 无非只充当个代理的角色。但是事务的传播行为却是 Spring 凭借自身的框架提供的功能,是 Spring 提供给开发者最珍贵的礼物,讹传的说法玷污了 Spring 事务框架最美丽的光环。
所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring 支持 7 种事务传播行为:
- PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
- PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
下面,我们来看一下实例,UserService#logon() 方法内部调用了 UserService#updateLastLogonTime() 和 ScoreService#addScore() 方法,这两个类都继承于 BaseService。它们之间的类结构说明如下:
图 1. UserService 和 ScoreService
具体的代码如下所示:
@Service("userService") public class UserService extends BaseService { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private ScoreService scoreService; public void logon(String userName) { updateLastLogonTime(userName); scoreService.addScore(userName, 20); } public void updateLastLogonTime(String userName) { String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?"; jdbcTemplate.update(sql, System.currentTimeMillis(), userName); } } |
UserService 中注入了 ScoreService 的 Bean,ScoreService 的代码如下所示:
@Service("scoreUserService") public class ScoreService extends BaseService{ @Autowired private JdbcTemplate jdbcTemplate; public void addScore(String userName, int toAdd) { String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?"; jdbcTemplate.update(sql, toAdd, userName); } } |
通过 Spring 的事务配置为 ScoreService 及 UserService 中所有公有方法都添加事务增强,让这些方法都工作于事务环境下。下面是关键的配置代码:
<!-- 添加Spring事务增强 --> <aop:config proxy-target-class="true"> <aop:pointcut id="serviceJdbcMethod" <!-- 所有继承于BaseService类的子孙类的public方法都进行事务增强--> expression="within(user.nestcall.BaseService+)"/> <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/> </aop:config> <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> |
将日志级别设置为 DEBUG,启动 Spring 容器并执行 UserService#logon() 的方法,仔细观察如下的输出日志:
16:25:04,765 DEBUG (AbstractPlatformTransactionManager.java:365) - Creating new transaction with name [user.nestcall.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT ①为UserService#logon方法启动一个事务 16:25:04,765 DEBUG (DataSourceTransactionManager.java:205) - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@32bd65] for JDBC transaction logon method... updateLastLogonTime... ②直接执行updateLastLogonTime方法 16:25:04,781 DEBUG (JdbcTemplate.java:785) - Executing prepared SQL update 16:25:04,781 DEBUG (JdbcTemplate.java:569) - Executing prepared SQL statement [UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?] 16:25:04,828 DEBUG (JdbcTemplate.java:794) - SQL update affected 0 rows 16:25:04,828 DEBUG (AbstractPlatformTransactionManager.java:470) - Participating in existing transaction ③ScoreService#addScore方法加入到UserService#logon的事务中 addScore... 16:25:04,828 DEBUG (JdbcTemplate.java:785) - Executing prepared SQL update 16:25:04,828 DEBUG (JdbcTemplate.java:569) - Executing prepared SQL statement [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?] 16:25:04,828 DEBUG (JdbcTemplate.java:794) - SQL update affected 0 rows 16:25:04,828 DEBUG (AbstractPlatformTransactionManager.java:752) - Initiating transaction commit ④提交事务 16:25:04,828 DEBUG (DataSourceTransactionManager.java:265) - Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@32bd65] 16:25:04,828 DEBUG (DataSourceTransactionManager.java:323) - Releasing JDBC Connection [org.apache.commons.dbcp.PoolableConnection@32bd65] after transaction 16:25:04,828 DEBUG (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource |
从上面的输入日志中,可以清楚地看到 Spring 为 UserService#logon() 方法启动了一个新的事务,而 UserSerive#updateLastLogonTime() 和 UserService#logon() 是在相同的类中,没有观察到有事务传播行为的发生,其代码块好像“直接合并”到 UserService#logon() 中。接着,当执行到 ScoreService#addScore() 方法时,我们就观察到了发生了事务传播的行为:Participating in existing transaction,这说明 ScoreService#addScore() 添加到 UserService#logon() 的事务上下文中,两者共享同一个事务。所以最终的结果是 UserService 的 logon(), updateLastLogonTime() 以及 ScoreService 的 addScore 都工作于同一事务中。
相关推荐
在定义方法时,一个方法内不能再定义另一个方法,即不能嵌套定义,但是在调用一个方法的过程中,还可以调用另一个方法,这是方法的嵌套调用。 方法的嵌套调用 假设main方法中调用a方法,a 方法中调用b方法,具体流程...
2. **递归方法(含嵌套调用)**:在这种方法中,我们使用函数自身来调用自身,直到达到基本情况(通常是n=1)。这种方法虽然直观,但可能会导致栈溢出,尤其是对于大数的阶乘。以下是一个使用嵌套调用的阶乘函数示例...
Dll调用与嵌套调用 一.Win32动态链接库 1.制作的步骤: (1)新建WIN32 Dynamic-link Library工程,工程名为MyDll,选择A simple DLL project类型。 (2)MyDll.h的内容如下: 以下是引用片段: extern "C" _...
Oracle 数据完整性嵌套事务调用分析研究 Oracle 数据库中,数据完整性是指数据的正确性、完整性和一致性。为了保护数据的完整性,我们可以使用多种方法,例如数据表的主键约束、外键约束、触发器等等。在处理数据...
WINCE 下 dll 嵌套调用 a.exe里面调用b.dll 然后再 b.dll调用c.dll 不过指针传递还未成功 谁成功了告诉下我 谢谢
数据库的嵌套调用,让初学者很好的学习数据库的使用和掌握一些调用方法
C语言函数的嵌套调用和递归调用 本文主要介绍C语言函数的嵌套调用和递归调用,包括函数的递归调用、变量的作用域和存储类型等知识点。 函数的递归调用 函数的递归调用是指函数直接或间接地自我调用的一种调用方式...
C语言函数的嵌套调用和递归调用学习教案.pptx
程序设计-函数的嵌套调用 函数的嵌套调用是程序设计中的一种重要概念。根据C语言规定,函数的定义不能嵌套,也就是说,在一个函数内不能定义其他函数,但是函数的调用可以嵌套。 函数的嵌套调用可以让程序呈现出...
### Java方法调用详解 #### 一、方法调用概览 在Java编程语言中,方法(也称为函数)是程序的基本构建块之一,用于封装特定功能以便在需要时重复使用。方法调用是实现这一功能的关键步骤。通过调用方法,我们可以...
springboot mybatis多数据源加事务嵌套 事务之间的调用 回滚 亲测可用 定义2个库分别建立 CREATE TABLE `user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户编号', `user_name` varchar(25) ...
通过分析和运行这些示例,开发者可以更直观地了解和掌握动态库嵌套调用的实践方法。 总结来说,C/C++的动态库嵌套调用是一项强大的技术,它使得软件组件能够灵活地交互和扩展。通过正确地创建、加载和管理动态库,...
当我们遇到"AOP实现自我调用的事物嵌套问题"时,这通常涉及到Spring框架中的事务管理,特别是自调用方法在事务处理时可能会引发的问题。 首先,让我们理解Spring AOP的事务管理是如何工作的。Spring使用代理模式来...
本教学单元将深入探讨两个关键概念——函数的嵌套调用和递归调用,它们是C语言高级编程技巧的基础。 1. **函数的嵌套调用**: 函数嵌套调用是指在一个函数的执行过程中调用了另一个函数。这种调用方式使得程序结构...
2. 开启嵌套事务:在已经开启的事务中,你可以再次调用`Tx.Begin()`来创建一个新的子事务。子事务将继承父事务的上下文,这意味着如果父事务回滚,所有的子事务也会回滚。 3. 提交和回滚:在嵌套事务中,每个子事务...
UG对话框多重调用和嵌套调用在模具CAD中的应用.pdf.crdownload
本章主要探讨了函数的两种特殊调用方式:嵌套调用和递归调用,特别在C语言程序设计的上下文中。 1. **函数的嵌套调用**:在C语言中,虽然函数不能定义在另一个函数内部,但可以在一个函数的执行过程中调用另一个...
在小学信息技术课程中,"过程的嵌套调用"是一个重要的概念,它涉及到编程思维的培养和逻辑结构的理解。本节课主要目的是让学生理解什么是过程的嵌套调用,并能熟练运用这一技巧进行图形绘制。 1. **过程(Procedure...
C语言函数的嵌套调用和递归调用 本学习教案主要介绍了C语言函数的嵌套调用和递归调用,包括函数的递归调用概念、变量的作用域和存储类型等知识点。 一、函数的递归调用 递归函数是指函数直接或间接地自我调用的...