• Spring事务传播机制回顾
  • 相互嵌套的服务方法
  • 源码

Spring事务传播机制回顾

关于Spring事务的一个错误的说法:一个事务方法中不应该调用另外一个事务方法,否则将产生两个事务,其实这是不正确的。

这是因为未正确认识Spring事务传播机制而造成的误解。 Spring对事务控制的支持统一在TransactionDefinition类中描述

我们来看下该类中的接口方法

  • int getPropagationBehavior() 事务的传播行为
  • int getIsolationLevel(); 事务的隔离级别
  • int getTimeout();事务的过期时间
  • boolean isReadOnly();事务的读、写特性
  • String getName();事务的名称

除了事务的传播行为外,事务的其他特性Spring是借助底层资源的功能来完成的,Spring无非只充当个代理的角色。但是事务的传播行为却是Spring凭借自身的框架提供的功能

所谓事务传播的行为,就是多个事务方法相互调用时,事务如何在这些方法间传播。 Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:

事务传播行为类型 说明
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。

Spring默认的事务传播行为是PROPAGATION_REQUESTED, 它适合绝大多数情况。

如果多个ServiceX#methodX() 均工作下在事务环境下(均被Spring事务增强),且程序中存在调用链Service1#method1()—->Service2#method2()——>Service#method3(),那么这3个服务类的3个方法通过Spring的事务传播机制都工作在同一个事务中。


相互嵌套的服务方法

我们来举个例子,TeacherService#doSomething()方法内部调用了 调用本类的udpateTeacherInfo还有StudentService#updateSutdent()方法,这两个类都继承于BaseService,类结构如下:

package com.xgj.dao.transaction.nestedCall.service;import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import com.xgj.dao.transaction.nestedCall.dao.TeacherDao;
import com.xgj.dao.transaction.nestedCall.domain.Student;
import com.xgj.dao.transaction.nestedCall.domain.Teacher;/*** * * @ClassName: TeacherService* * @Description: @Service标注的service层 继承BaseService* * @author: Mr.Yang* * @date: 2017年9月24日 下午4:56:35*/@Service
public class TeacherService extends BaseService {Logger logger = Logger.getLogger(TeacherService.class);private TeacherDao teacherDao;private StudentService studentService;@Autowiredpublic void setTeacherDao(TeacherDao teacherDao) {this.teacherDao = teacherDao;}@Autowiredpublic void setStudentService(StudentService studentService) {this.studentService = studentService;}/*** * * @Title: init* * @Description: 改方法嵌套调用了本类的其他方法以及其他类的方法* * * @return: void*/public void doSomething() {logger.info("before TeacherService#udpateTeacherInfo");// 调用本类的其他方法udpateTeacherInfo(simulateTeacher());logger.info("after TeacherService#udpateTeacherInfo");// 调用其他类的方法logger.info("before StudentService#updateSutdent");studentService.updateSutdent(simulateStudent());logger.info("after StudentService#updateSutdent");}public void udpateTeacherInfo(Teacher teacher) {teacherDao.updateTeacher(teacher);}/*** * * @Title: simulateTeacher* * @Description: 模拟获取一个teacher实例* * @return* * @return: Teacher*/private Teacher simulateTeacher() {Teacher teacher = new Teacher();teacher.setName("FTT");teacher.setAge(88);teacher.setSex("FF");teacher.setTeacherId(2);return teacher;}private Student simulateStudent() {Student student = new Student();student.setName("FSS");student.setAge(22);student.setSex("MM");student.setStudentId(2);return student;}}

配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 --><context:component-scan base-package="com.xgj.dao.transaction.nestedCall" /><!-- 使用context命名空间,配置数据库的properties文件 --><context:property-placeholder location="classpath:spring/jdbc.properties" /><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close" p:driverClassName="${jdbc.driverClassName}"p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}" /><!-- 配置Jdbc模板 --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"p:dataSource-ref="dataSource" /><!--事务管理器,通过属性引用数据源 --><bean id="jdbcManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"p:dataSource-ref="dataSource"/><!-- 通过以下配置为继承BaseService的所有子类的所有public方法添加事务增强 --><aop:config  proxy-target-class="true"><!-- 切点 --><aop:pointcut  id="serviceJdbcMethod" expression="within(com.xgj.dao.transaction.nestedCall.service.BaseService+)"/><!-- 切面 --><aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="txAdvice"/></aop:config><!-- 增强,供aop:advisor引用 --><tx:advice id="txAdvice" transaction-manager="jdbcManager"><tx:attributes><tx:method name="*"/></tx:attributes></tx:advice></beans>

通过Spring配置为TeacherService以及StudentService中的所有公共方法都添加Spring AOP的事务增强,让TeacherService的doSomething()和udpateTeacherInfo()以及StudentService的updateSutdent方法都工作在事务环境下。

关键代码:

<aop:pointcut  id="serviceJdbcMethod" expression="within(com.xgj.dao.transaction.nestedCall.service.BaseService+)"/>

我们将日志级别调整为DEBUG,运行测试类

package com.xgj.dao.transaction.nestedCall.service;import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TeacherServiceTest {ClassPathXmlApplicationContext ctx = null;TeacherService teacherService = null;@Beforepublic void initContext() {// 启动Spring 容器ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/dao/transaction/nestedCall/conf_tx_nestedCall.xml");teacherService = ctx.getBean("teacherService", TeacherService.class);System.out.println("initContext successfully");}@Testpublic void testNestedCallInOneTransaction() {teacherService.doSomething();}@Afterpublic void closeContext() {if (ctx != null) {ctx.close();}System.out.println("close context successfully");}
}

观察日志:

2017-09-24 18:36:23,428 DEBUG [main] (AbstractPlatformTransactionManager.java:367) - Creating new transaction with name [com.xgj.dao.transaction.nestedCall.service.TeacherService.doSomething]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2017-09-24 18:36:23,777 DEBUG [main] (DataSourceTransactionManager.java:248) - Acquired Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] for JDBC transaction
2017-09-24 18:36:23,781 DEBUG [main] (DataSourceTransactionManager.java:265) - Switching JDBC Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] to manual commit
2017-09-24 18:36:23,820  INFO [main] (TeacherService.java:52) - before TeacherService#udpateTeacherInfo
2017-09-24 18:36:23,824 DEBUG [main] (JdbcTemplate.java:869) - Executing prepared SQL update
2017-09-24 18:36:23,825 DEBUG [main] (JdbcTemplate.java:616) - Executing prepared SQL statement [update teacher set  name = ? ,age = ? ,sex = ?  where id = ?]
2017-09-24 18:36:23,974 DEBUG [main] (JdbcTemplate.java:879) - SQL update affected 1 rows
2017-09-24 18:36:23,978  INFO [main] (TeacherDaoImpl.java:64) - updateTeacher successfully
2017-09-24 18:36:23,978  INFO [main] (TeacherService.java:55) - after TeacherService#udpateTeacherInfo
2017-09-24 18:36:23,978  INFO [main] (TeacherService.java:58) - before StudentService#updateSutdent
2017-09-24 18:36:23,978 DEBUG [main] (AbstractPlatformTransactionManager.java:476) - Participating in existing transaction
2017-09-24 18:36:24,004 DEBUG [main] (JdbcTemplate.java:869) - Executing prepared SQL update
2017-09-24 18:36:24,005 DEBUG [main] (JdbcTemplate.java:616) - Executing prepared SQL statement [update student set  name = ? ,age = ? ,sex = ?  where id = ?]
2017-09-24 18:36:24,007 DEBUG [main] (JdbcTemplate.java:879) - SQL update affected 1 rows
2017-09-24 18:36:24,007  INFO [main] (StudentDaoImpl.java:82) - updateStudent  successfully
2017-09-24 18:36:24,008  INFO [main] (TeacherService.java:60) - after StudentService#updateSutdent
2017-09-24 18:36:24,008 DEBUG [main] (AbstractPlatformTransactionManager.java:759) - Initiating transaction commit
2017-09-24 18:36:24,008 DEBUG [main] (DataSourceTransactionManager.java:310) - Committing JDBC transaction on Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver]

Creating new transaction with name [com.xgj.dao.transaction.nestedCall.service.TeacherService.doSomething]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

为TeacherService#doSomething开启一个事务, 然后直接执行udpateTeacherInfo方法,由于doSomething和udpateTeacherInfo在一个类中,没有观察到有事务传播行为的发生,

然而当执行到updateSutdent方法时,我们观察到一个事务传播行为: Participating in existing transaction ,这说明 StudentService#updateSutdent方法添加到了TeacherService#doSomething()方法的事务上下文中,二者共享同一个事务。 所以最终的结果是TeacherService的doSomething 和 udpateTeacherInfo 以及 StudentService#updateSutdent()方法工作在同一个事务中。

最后 Initiating transaction commit—-提交事务


源码

代码已托管到Github—> https://github.com/yangshangwei/SpringMaster

Spring JDBC-事务方法嵌套调用解读相关推荐

  1. 在事务方法中调用另外一个事务方法,被调用方法的事务没起作用

    在事务方法中调用另外一个事务方法,被调用方法的事务没起作用 在事务方法中调用另外一个事务方法,被调用方法的事务没起作用 问题描述:(例) service层有两个事务方法insertUser1.inse ...

  2. Spring 事务方法与非事务方法相互调用 @Transactional 注解失效不回滚?

    写这篇文章的初衷呢就是最近遇到了一个Spring事务的大坑.与其说是坑,还不如说是自己事务这块儿太薄弱导致的(自嘲下). 项目环境 Spring Boot 下面开始问题描述,发生的过程有点长,想直接看 ...

  3. 如何解决Spring在同类方法相互调用中,事务,缓存等注解不生效的问题

    ☀️相信在日常开发中,一定经历过这样一个场景,就以下面这段代码抽象一下: @Service public class ServiceA {public void methodA() {methodB( ...

  4. Spirng的事务 方法A调用方法B,事务是否失效

    总结: 方法A调用方法B: 如果A和B方法在同一个类中: 如果A加@Transactional注解,B加不加@Transactional注解,事务是有效的,则AB在同一事务中. 如果A不加@Trans ...

  5. Spring JDBC事务支持类jdbcTemplate(了解)

    之前的JDBC中,我们通常使用PreparedStatement类实现事务. 下面简单介绍JdbcTemplate的用法: 1.查询单个对象 2.查询集合 3.插入.更新.删除操作 4.批量操作

  6. Spring4.X系列之Spring JDBC

    专栏 导读 源码 专栏 欢迎关注 : Spring-JDBC手札 导读 Spring JDBC-Spring对DAO的支持 Apache-DBCP数据库连接池解读 C3P0-数据库连接池解读 Spri ...

  7. mybatis和spring jdbc持久层框架事务支持分析

    mybatis和spring jdbc持久层框架事务支持分析 ​ 持久层框架中的事务支持指的是持久层框架如何支持数据库事务,我们先梳理出原生数据库事务操作的主线脉络,它是通过java.sql 包下的C ...

  8. spring配置JDBC事务

    http://www.iteye.com/problems/2951 Spring+JDBC事务配置 悬赏:10 发布时间:2008-08-19 提问人:charity_lan (初级程序员) < ...

  9. spring注解事务使用总结

    在使用spring的注解事务的时候,需要考虑到事务的传播行为.遇到什么类型的异常时,事务才起作用.事务方法之间的嵌套调用时,怎么样才生效等等诸多问题.网上搜到很多的主要还是一堆理论文字描述,我这里给出 ...

最新文章

  1. 地图旋转_折纸效果三维旋转,不一样的地图页设计
  2. Mac的brew和brew cask区别以及安装brew cask
  3. 开发日记-20190612 关键词 读书笔记《鸟哥的Linux私房菜-基础学习篇》
  4. 通过java反射机制获取该类的所有属性类型、值。
  5. C++中函数参数形式的总结
  6. MySQL创建和操纵数据库和表(DDL)最全总结(小白都能能懂哦)
  7. Qt Creator指定文本编辑器设置
  8. nginx 源码调试
  9. Linux文件目录及其作用
  10. 【传智播客】JavaWeb程序设计任务教程 第一章练习答案
  11. 工作97:父子组件传值
  12. 一手云端,一手终端:比特大陆发布两款AI芯片,大步迈进AI领域
  13. # Day8:类的方法、三大特征、装饰器、组合、多态、设计模式
  14. 0x00007FF73361E515 处(位于 基于多态的职工管理系统.exe 中)引发的异常: 0xC0000005: 职工岗位输入不是1,2,3,而是其他乱七八糟的
  15. 半导体储存器例题小试--十安辰
  16. python 矩阵化为最简阶梯型
  17. 【JAVA】Java 内存模型中的 happen-before
  18. IT 通信类客户分析
  19. PostgreSQL 10.1 手册_部分 III. 服务器管理_第 30 章 可靠性和预写式日志_30.4. WAL配置...
  20. crs-2632 crs-2674

热门文章

  1. java lambda collect_45分钟学会Java8 - Lambda和Stream
  2. C++中的RAII机制
  3. Python 非线性方程组
  4. 文巾解题 461. 汉明距离
  5. Linux疑难杂症解决方案100篇(七)-SHELL编程变量与四则运算
  6. 【Python刷题】_8
  7. Python编程基础:第四节 类型转换Type Cast
  8. 第7章 PCA与梯度上升法
  9. Kafka设计解析(四):Kafka Consumer解析--转
  10. tomcat:there is no resources that can be added or removed from server