javascript
Spring JDBC-Spring对事务管理的支持
- 概述
- 事務管理關鍵抽象
- Spring事務管理的實現類
- Spring JDBC 和MybBatis的事務管理器的配置
- JPA的事務管理器的配置
- Hibernate的事務管理器的配置
- JTA 的事務管理器的配置
- 事務同步管理器
- 事務的傳播行為
- 示例
- 編程式的事務管理
- 示例
概述
Spring為事務管理提供了一致的編程模板,在高層次建立了統一的事務抽象。也就是說,不管選擇Spring JDBC、Hibernate 、JPA 還是iBatis,Spring都讓我們可以用統一的編程模型進行事務管理。
類似Spring DAO 為不同的持久化技術實現提供了模板類一樣,Spring事務管理也提供了事務模板類TransactionTemplate。 通過TransactionTemplate并配合使用事務回調TransactionCallback指定具體的持久化操作,就可以 通過編程的方式實現事務管理,而無須關注資源獲取、復用、釋放、事務同步和異步處理等操作。
Spring事務管理的亮點在于聲明式事務管理,Spring允許通過聲明的方式,在IoC配置中指定事務的邊界和事務屬性,Spring會自動在指定的事務邊界上應用事務屬性。
事務管理關鍵抽象
在Spring事務管理SPI(Service Provider Interface)的抽象層主要包括3個接口,分別是PlatformTransactionManager、TransactionDefinition和TransactionStatus。 都在org.springframework.transaction包中。
TransactionDefinition用于描述事務的隔離級別、超時時間、是否為只讀事務和事務傳播規則等控制事務具體行為的事務屬性,這些事務屬性可以通過XML配置或注解描述提供,也可以通過手工編程的方式設置。
PlatformTransactionManager根據TransactionDefinition提供的事務屬性配置信息,創建事務,并用TransactionStatus描述這個激活事務的狀態。
Spring事務管理的實現類
spring將事務管理委托底層具體的持久化實現框架去完成,因此針對不同的框架spring有的不同的接口實現類.
| org.springframework.orm.jpa.JpaTransactionManager | 使用JPA進行持久化時,使用該事務管理器 |
| org.springframework.orm.hibernateX.HibernateTransactionManager | 使用HibernateX版本時使用該事務管理器 |
| org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用SpringJDBC或MyBatis等基于DataSource數據源的持久化技術時,使用該事務管理器 |
| org.springframework.orm.jdo.JdoTransactionManager | 使用JDO進行持久化時,使用該事務管理器 |
| org.springframework.transaction.jta.JtaTransactionManager | 具有多個數據源的全局事務使用該事務管理器(不管采用何種持久化技術) |
要實現事務管理,首先要在Spring中配置好相應的事務管理器,為事務管理器指定數據資源及一些其他事務管理控制屬性。
下面介紹一下幾個常見的事務管理器的配置
Spring JDBC 和MybBatis的事務管理器的配置
Spring JDBC 和MybBatis都是基于數據源的Connection訪問數據庫,所有都可以使用DataSourceTransactionManager, 配置如下
<!--引用外部的Properties文件--> <context:property-placeholder location="classpath: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}"/><!--基于數據源的事務管理器,通過屬性引用數據源--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"p:dataSource-ref="dataSource"/>JPA的事務管理器的配置
要配置一個JPA事務管理器,必須現提供一個DataSource,然后配置一個EntityManagerFactory,最后才配置JpaTransationManager.
.......<!--通過dataSource-ref指定一個數據源--> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"p:dataSource-ref="dataSource"/>...... </bean><!--指定實體管理器--><bean id="transactionManger" class="org.springframework.orm.jpa.JpaTransactionManager"p:entityManagerFacotry-ref="entityManagerFactory"/>Hibernate的事務管理器的配置
Spring4.0已經取消了對Hibernate3.6之前的版本支持,并全面支持Hibernate5.0. 因此,只為Hibernate3.6+提供事務管理器。
以Hibernate4.0為例
.... <!--通過dataSource-ref引用數據源 和 Hibernate配置文件 及其他屬性--> <bean id="sessionFactory"class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"p:dataSource-ref="dataSource"p:mappingResources="classpath:Artisan.hbm.xml"><property name="hibernateProperties"><props><prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop><prop key="hibernate.show_sql">true</prop><prop key="hibernate.generate_statistics">true</prop></props></property> </bean><bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"p:sessionFactory-ref="sessionFactory"/>JTA 的事務管理器的配置
如果希望在JavaEE容器中使用JTA,則將通過JNDI和Spring的JtaTransactionManager獲取一個容器的DataSource。
<!--通過jee命名空間獲取Java EE應用服務器容器中的數據源--> <jee:jndi-lookup id="accountDs" jndi-name="java:comp/env/jdbc/account"/> <jee:jndi-lookup id="orderDs" jndi-name="java:comp/env/jdbc/account"/><!--指定JTA事務管理器。--> <bean id="transactionManager"class="org.springframework.transaction.jta.JtaTransactionManager"/>事務同步管理器
Spring將JDBC的Connection、Hibernate的Session等訪問數據庫的連接或者會話對象統稱為資源,這些資源在同一時刻是不能多線程共享的。
為了讓DAO、Service類可能做到singleton, Spring的事務同步管理類org.springframework.transaction.support.TransactionSynchronizationManager使用ThreadLocal為不同事務線程提供了獨立的資源副本,同時維護事務配置的屬性和運行狀態信息。
事務同步管理器是Spring事務管理的基石,不管用戶使用的是編程式事務管理,還是聲明式事務管理,都離不開事務同步管理器。
Spring框架為不同的持久化技術提供了一套從TransactionSynchronizationManager中獲取對應線程綁定資源的工具類
| Spring JDBC或者MyBatis | org.springframework.jdbc.datasource.DataSourceUtils |
| HibernateX.0 | org.springframework.orm.hibernateC.SessionFactoryUtils |
| JPA | org.springframework.orm.jpa.EntityManagerFactoryUtils |
| JDO | org.springframework.orm.jdo.PersistenceManagerFactoryUtils |
這些工具類都提供了靜態的方法,通過這些方法可以獲取和當前線程綁定的資源,如
DataSourceUtils.getConnection (DataSource
dataSource)可以從指定的數據源中獲取和當前線程綁定的ConnectionHibernate的SessionFactoryUtils.getSession (SessionFactory
sessionFactory, boolean allowCreate)則從指定的SessionFactory中獲取和當前線程綁定的Session。當需要脫離模板類,手工操作底層持久技術的原生API時,就需要通過這些工具類獲取線程綁定的資源,而不應該直接從DataSource或SessionFactory中獲取。因為后者不能獲得和本線程相關的資源,因此無法讓數據操作參與到本線程相關的事務環境中。
這些工具類還有另外一個重要的用途:將特定異常轉換為Spring的DAO異常。
Spring為不同的持久化技術提供了模板類,模板類在內部通過資源獲取工具類間接訪問TransactionSynchronizationManager中的線程綁定資源。所以,如果Dao使用模板類進行持久化操作,這些Dao就可以配置成singleton。如果不使用模板類,也可直接通過資源獲取工具類訪問線程相關的資源。
我們來開下TransactionSynchronizationManager的面紗:
TransactionSynchronizationManager將Dao、Service類中影響線程安全的所有“狀態”統一抽取到該類中,并用ThreadLocal進行替換,從此Dao(必須基于模板類或資源獲取工具類創建的Dao)和Service(必須采用Spring事務管理機制)摘掉了非線程安全的帽子,完成了脫胎換骨式的身份轉變。
事務的傳播行為
當我們調用一個基于Spring的Service接口方法(如UserService#addUser())時,它將運行于Spring管理的事務 環境中,Service接口方法可能會在內部調用其它的Service接口方法以共同完成一個完整的業務操作,因此就會產生服務接口方法嵌套調用的情況, 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,并且實現者需要支持保存點事務機制。
示例
當服務接口方法分別使用表1中不同的事務傳播行為,且這些接口方法又發生相互調用的情況下,大部分組合都是一目了然,容易理解的。但是,也存在一些容易引起誤解的組合事務傳播方式。
下面,我們通過兩個具體的服務接口的組合調用行為來破解這一難點。這兩個服務接口分別是UserService和ForumService, UserSerice有一個addCredits()方法,ForumSerivce#addTopic()方法調用了 UserSerice#addCredits()方法,發生關聯性服務方法的調用:
@Service public class ForumService {private UserService userService;// ①調用其它服務接口的方法public void addTopic() {// ②被關聯調用的業務方法userService.addCredits();}public void setUserService(UserService userService) {this.userService = userService;}}嵌套調用的事務方法 : 對Spring事務傳播行為最常見的一個誤解是:當服務接口方法發生嵌套調用時,被調用的服務方法只能聲明為 PROPAGATION_NESTED。這種觀點犯了望文生義的錯誤,誤認為PROPAGATION_NESTED是專為方法嵌套準備的。這種誤解遺害不 淺,執有這種誤解的開發者錯誤地認為:應盡量不讓Service類的業務方法發生相互的調用,Service類只能調用DAO層的DAO類,以避免產生嵌 套事務。
其實,這種顧慮是完全沒有必要的,PROPAGATION_REQUIRED已經清楚地告訴我們:事務的方法會足夠“聰明”地判斷上下文是否已經存在一個事務中,如果已經存在,就加入到這個事務中,否則創建一個新的事務。
依照上面的例子,假設我們將ForumService#addTopic()和UserSerice#addCredits()方法的事務傳播行為都設置為PROPAGATION_REQUIRED,這兩個方法將運行于同一個事務中。
將ForumService#addTopic()設置為PROPAGATION_REQUIRED時, UserSerice#addCredits()設置為PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、 PROPAGATION_MANDATORY時,運行的效果都是一致的(當然,如果單獨調用addCredits()就另當別論了)。
當addTopic()運行在一個事務下(如設置為PROPAGATION_REQUIRED),而addCredits()設置為 PROPAGATION_NESTED時,如果底層數據源支持保存點,Spring將為內部的addCredits()方法產生的一個內嵌的事務。如果 addCredits()對應的內嵌事務執行失敗,事務將回滾到addCredits()方法執行前的點,并不會將整個事務回滾。內嵌事務是內層事務的一 部分,所以只有外層事務提交時,嵌套事務才能一并提交。
嵌套事務不能夠提交,它必須通過外層事務來完成提交的動作,外層事務的回滾也會造成內部事務的回滾。
嵌套事務和新事務
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED也是容易混淆的兩個傳播行為。PROPAGATION_REQUIRES_NEW 啟動一個新的、和外層事務無關的“內部”事務。該事務擁有自己的獨立隔離級別和鎖,不依賴于外部事務,獨立地提交和回滾。當內部事務開始執行時,外部事務 將被掛起,內務事務結束時,外部事務才繼續執行。
由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在于:
- PROPAGATION_REQUIRES_NEW 將創建一個全新的事務,它和外層事務沒有任何關系,
- 而 PROPAGATION_NESTED 將創建一個依賴于外層事務的子事務,當外層事務提交或回滾時,子事務也會連帶提交和回滾。
以下幾個問題值得注意:
1.當業務方法被設置為PROPAGATION_MANDATORY時,它就不能被非事務的業務方法調用。
如將ForumService#addTopic ()設置為PROPAGATION_MANDATORY,如果展現層的Action直接調用addTopic()方法,將引發一個異常。正確的情況是: addTopic()方法必須被另一個帶事務的業務方法調用(如ForumService#otherMethod())。所以 PROPAGATION_MANDATORY的方法一般都是被其它業務方法間接調用的。2 當業務方法被設置為PROPAGATION_NEVER時,它將不能被擁有事務的其它業務方法調用。
假設UserService#addCredits ()設置為PROPAGATION_NEVER,當ForumService# addTopic()擁有一個事務時,addCredits()方法將拋出異常。所以PROPAGATION_NEVER方法一般是被直接調用的。3 當方法被設置為PROPAGATION_NOT_SUPPORTED時,外層業務方法的事務會被掛起,當內部方法運行完成后,外層方法的事務重新運行。如果外層方法沒有事務,直接運行,不需要做任何其它的事。
在Spring聲明式事務管理的配置中,事務傳播行為是最容易被誤解的配置項,原因在于事務傳播行為名稱(如 PROPAGATION_NESTED:嵌套式事務)和代碼結構的類似性上(業務類方法嵌套調用另一個業務類方法).
編程式的事務管理
在實際的應用中很少通過編程來進行事務管理,但是Spring還是為編程式事務管理提供了模板類 TransactionTemplate,以滿足一些特殊場合的要求。
TransactionTemplate是線程安全的,因此可以在多個類中共享TransactionTemplate實例進行事務管理。
TransactionTemplate主要有兩個方法:
public void setTransactionManager(PlatformTransactionManager transactionManager) 設置事務管理器
public <T> T execute(TransactionCallback<T> action) throws TransactionException 在TransactionCallback回調接口中定義需要以事務方式組織的數據訪問邏輯
TransactionCallback接口中僅有一個方法
protected void doInTransaction(TransactionStatus status)如果操作不需要返回結果,可以使用TransactionCallback的子接口 TransactionCallbackWithoutResult。
示例
代碼已托管到Github—> https://github.com/yangshangwei/SpringMaster
POJO
package com.xgj.dao.transaction.programTrans;import org.springframework.stereotype.Component;/*** * * @ClassName: Artisan* * @Description: @Component標注的Bean* * @author: Mr.Yang* * @date: 2017年9月18日 下午5:03:47*/@Component public class Artisan {private String userName;private String password;public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}} package com.xgj.dao.transaction.programTrans;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate;/*** * * @ClassName: ProgramTransService* * @Description: 在實際應用中,很少通過編程的方式來進行事務管理。* * @author: Mr.Yang* * @date: 2017年9月21日 下午3:48:10*/@Service public class ProgramTransService {private JdbcTemplate jdbcTemplate;private TransactionTemplate transactionTemplate;// 下面兩條SQL在一個事務中,第二條故意寫錯了表名,會執行失敗,第一條已經成功的SQL也會回滾private static final String addArtisanSQL = "insert into artisan_user(user_name,password) values(?,?)";private static final String deleteOneArtisanSQL = "delete from artisan_user1 where user_name = 'ArtisanBatch0' ";@Autowiredpublic void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}/*** * * @Title: setTransactionTemplate* * @Description: 通過AOP主動注入transactionTemplate* * @param transactionTemplate* * @return: void*/@Autowiredpublic void setTransactionTemplate(TransactionTemplate transactionTemplate) {this.transactionTemplate = transactionTemplate;}public void operArtisanInTrans(final Artisan artisan) {transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus status) {// 需要在事務中執行的邏輯jdbcTemplate.update(addArtisanSQL, artisan.getUserName(),artisan.getPassword());System.out.println("addArtisanSQL OK ");jdbcTemplate.update(deleteOneArtisanSQL);System.out.println("deleteOneArtisanSQL OK ");}});} }配置文件
<?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"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.xsd"><!-- 掃描類包,將標注Spring注解的類自動轉化Bean,同時完成Bean的注入 --><context:component-scan base-package="com.xgj.dao.transaction.programTrans" /><!-- 不使用context命名空間,則需要定義Bean <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:spring/jdbc.properties" /> </bean> --><!-- 使用context命名空間,同上面的Bean等效.在xml文件中配置數據庫的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="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"p:dataSource-ref="dataSource"/><!-- 配置transactionTemplate模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"p:transactionManager-ref="transactionManager"/></beans>單元測試
package com.xgj.dao.transaction.programTrans;import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext;public class ProgramTransServiceTest {ClassPathXmlApplicationContext ctx = null;@Beforepublic void initContext() {// 啟動Spring 容器ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/dao/transaction/programTrans/conf_program_transaction.xml");System.out.println("initContext successfully");}@Testpublic void testProgramTransaction() {Artisan artisan = ctx.getBean("artisan", Artisan.class);artisan.setUserName("trans");artisan.setPassword("123");ProgramTransService programTransService = ctx.getBean("programTransService", ProgramTransService.class);programTransService.operArtisanInTrans(artisan);System.out.println("testProgramTransaction successsfully");}@Afterpublic void closeContext() {if (ctx != null) {ctx.close();}System.out.println("close context successfully");}}運行結果
第二條因為執行失敗,第一條也回滾了,未插入數據, OK。
總結
以上是生活随笔為你收集整理的Spring JDBC-Spring对事务管理的支持的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring-AOP 混合使用各种切面类
- 下一篇: Spring4.X系列之IOC