javascript
Spring JDBC-事务方法嵌套调用解读
- 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-事务方法嵌套调用解读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring JDBC-使用注解配置声明
- 下一篇: JavaScript-语法、关键保留字及