`

事务方法嵌套调用的迷茫

 
阅读更多

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
图 1. UserService 和 ScoreService 

具体的代码如下所示:


清单 9 UserService.java

				
@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 的代码如下所示:


清单 10 ScoreService.java

				
@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 中所有公有方法都添加事务增强,让这些方法都工作于事务环境下。下面是关键的配置代码:


清单 11 事务增强配置

				
<!-- 添加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() 的方法,仔细观察如下的输出日志:


清单 12 执行日志

				
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 都工作于同一事务中。

分享到:
评论

相关推荐

    Java程序设计基础:调用方法-方法的嵌套调用.pptx

    在定义方法时,一个方法内不能再定义另一个方法,即不能嵌套定义,但是在调用一个方法的过程中,还可以调用另一个方法,这是方法的嵌套调用。 方法的嵌套调用 假设main方法中调用a方法,a 方法中调用b方法,具体流程...

    嵌套调用求阶乘和_C语言_嵌套调用_

    2. **递归方法(含嵌套调用)**:在这种方法中,我们使用函数自身来调用自身,直到达到基本情况(通常是n=1)。这种方法虽然直观,但可能会导致栈溢出,尤其是对于大数的阶乘。以下是一个使用嵌套调用的阶乘函数示例...

    Dll调用与嵌套调用

    Dll调用与嵌套调用 一.Win32动态链接库  1.制作的步骤:  (1)新建WIN32 Dynamic-link Library工程,工程名为MyDll,选择A simple DLL project类型。  (2)MyDll.h的内容如下: 以下是引用片段: extern "C" _...

    Oracle数据完整性嵌套事务调用分析研究

    Oracle 数据完整性嵌套事务调用分析研究 Oracle 数据库中,数据完整性是指数据的正确性、完整性和一致性。为了保护数据的完整性,我们可以使用多种方法,例如数据表的主键约束、外键约束、触发器等等。在处理数据...

    WINCE dll 嵌套调用

    WINCE 下 dll 嵌套调用 a.exe里面调用b.dll 然后再 b.dll调用c.dll 不过指针传递还未成功 谁成功了告诉下我 谢谢

    实验三: 数据库的嵌套调用

    数据库的嵌套调用,让初学者很好的学习数据库的使用和掌握一些调用方法

    C语言函数的嵌套调用和递归调用PPT课件.pptx

    C语言函数的嵌套调用和递归调用 本文主要介绍C语言函数的嵌套调用和递归调用,包括函数的递归调用、变量的作用域和存储类型等知识点。 函数的递归调用 函数的递归调用是指函数直接或间接地自我调用的一种调用方式...

    C语言函数的嵌套调用和递归调用学习教案.pptx

    C语言函数的嵌套调用和递归调用学习教案.pptx

    程序设计-函数的嵌套调用.pptx

    程序设计-函数的嵌套调用 函数的嵌套调用是程序设计中的一种重要概念。根据C语言规定,函数的定义不能嵌套,也就是说,在一个函数内不能定义其他函数,但是函数的调用可以嵌套。 函数的嵌套调用可以让程序呈现出...

    java方法调用

    ### Java方法调用详解 #### 一、方法调用概览 在Java编程语言中,方法(也称为函数)是程序的基本构建块之一,用于封装特定功能以便在需要时重复使用。方法调用是实现这一功能的关键步骤。通过调用方法,我们可以...

    springboot mybatis多数据源加事务嵌套

    springboot mybatis多数据源加事务嵌套 事务之间的调用 回滚 亲测可用 定义2个库分别建立 CREATE TABLE `user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户编号', `user_name` varchar(25) ...

    C/C++ 动态库嵌套调用源代码文件

    通过分析和运行这些示例,开发者可以更直观地了解和掌握动态库嵌套调用的实践方法。 总结来说,C/C++的动态库嵌套调用是一项强大的技术,它使得软件组件能够灵活地交互和扩展。通过正确地创建、加载和管理动态库,...

    AOP实现自我调用的事物嵌套问题

    当我们遇到"AOP实现自我调用的事物嵌套问题"时,这通常涉及到Spring框架中的事务管理,特别是自调用方法在事务处理时可能会引发的问题。 首先,让我们理解Spring AOP的事务管理是如何工作的。Spring使用代理模式来...

    C语言编程技术实践 函数嵌套调用和递归调用教学单元设计.doc

    本教学单元将深入探讨两个关键概念——函数的嵌套调用和递归调用,它们是C语言高级编程技巧的基础。 1. **函数的嵌套调用**: 函数嵌套调用是指在一个函数的执行过程中调用了另一个函数。这种调用方式使得程序结构...

    dedecms实现在标签中嵌套调用sql的方法.doc

    dedecms实现在标签中嵌套调用sql的方法.doc

    Go-Sqalx-sqlx嵌套事务

    2. 开启嵌套事务:在已经开启的事务中,你可以再次调用`Tx.Begin()`来创建一个新的子事务。子事务将继承父事务的上下文,这意味着如果父事务回滚,所有的子事务也会回滚。 3. 提交和回滚:在嵌套事务中,每个子事务...

    UG对话框多重调用和嵌套调用在模具CAD中的应用.pdf.crdownload

    UG对话框多重调用和嵌套调用在模具CAD中的应用.pdf.crdownload

    第6章 函数-5函数的嵌套调用和递归调用1

    本章主要探讨了函数的两种特殊调用方式:嵌套调用和递归调用,特别在C语言程序设计的上下文中。 1. **函数的嵌套调用**:在C语言中,虽然函数不能定义在另一个函数内部,但可以在一个函数的执行过程中调用另一个...

    六年级信息上册 第8课过程的嵌套调用教案 人教版.doc

    在小学信息技术课程中,"过程的嵌套调用"是一个重要的概念,它涉及到编程思维的培养和逻辑结构的理解。本节课主要目的是让学生理解什么是过程的嵌套调用,并能熟练运用这一技巧进行图形绘制。 1. **过程(Procedure...

Global site tag (gtag.js) - Google Analytics