Spring JDBC-事务方法嵌套调用解读
2017-09-24 13:38
232 查看
Spring事务传播机制回顾
相互嵌套的服务方法
源码
这是因为未正确认识Spring事务传播机制而造成的误解。 Spring对事务控制的支持统一在TransactionDefinition类中描述
我们来看下该类中的接口方法
int getPropagationBehavior() 事务的传播行为
int getIsolationLevel(); 事务的隔离级别
int getTimeout();事务的过期时间
boolean isReadOnly();事务的读、写特性
String getName();事务的名称
除了事务的传播行为外,事务的其他特性Spring是借助底层资源的功能来完成的,Spring无非只充当个代理的角色。但是事务的传播行为却是Spring凭借自身的框架提供的功能。
所谓事务传播的行为,就是多个事务方法相互调用时,事务如何在这些方法间传播。 Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:
当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。
Spring默认的事务传播行为是PROPAGATION_REQUESTED, 它适合绝大多数情况。
如果多个ServiceX#methodX() 均工作下在事务环境下(均被Spring事务增强),且程序中存在调用链Service1#method1()—->Service2#method2()——>Service#method3(),那么这3个服务类的3个方法通过Spring的事务传播机制都工作在同一个事务中。
配置文件:
通过Spring配置为TeacherService以及StudentService中的所有公共方法都添加Spring AOP的事务增强,让TeacherService的doSomething()和udpateTeacherInfo()以及StudentService的updateSutdent方法都工作在事务环境下。
关键代码:
我们将日志级别调整为DEBUG,运行测试类
观察日志:
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—-提交事务
相互嵌套的服务方法
源码
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类似的操作。 |
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; @Autowired public void setTeacherDao(TeacherDao teacherDao) { this.teacherDao = teacherDao; } @Autowired public 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://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; @Before public 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"); } @Test public void testNestedCallInOneTransaction() { teacherService.doSomething(); } @After public 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事务传播特性的浅析——事务方法嵌套调用的迷茫
- Spring事务传播特性的浅析——事务方法嵌套调用的迷茫
- Spring事务传播特性的浅析——事务方法嵌套调用的迷茫
- Spring事务传播特性的浅析——事务方法嵌套调用的迷茫
- Spring的事务管理难点剖析(3):事务方法嵌套调用的迷茫
- Spring的事务管理难点剖析(3):事务方法嵌套调用的迷茫
- Spring事务传播特性的浅析——事务方法嵌套调用的迷茫
- Spring事务传播特性的浅析——事务方法嵌套调用
- Spring的事务管理难点剖析:事务方法嵌套调用的迷茫
- WCF分布式开发常见错误(13):此方法调用的事务被异步中断
- spring 事宜方法嵌套调用的迷茫
- spring aop 嵌套调用的问题 (同一方法内调用切面切不到)
- 多Fragment嵌套是如何调用父Fragment的onActivityResult()方法
- 【事务】<查询不到同一调用方法其它事务提交的更新>解决方案
- Fragment嵌套Fragment时候。子类fragment调用父容器Fragment方法
- html页面中iframe嵌套页面的父页面和子页面js方法互相调用
- MySQL存储过程例子,包含事务,参数,嵌套调用,游标,循环等
- 转 业务层中 被调用服务的遭遇事务回滚的处理 2方法
- spring本类中调用事务方法不生效
- spring声明式事务 同一类内方法调用事务失效