javascript
Spring JDBC-Spring对事务管理的支持
- 概述
- 事務管理關鍵抽象
- Spring事務管理的實現(xiàn)類
- Spring JDBC 和MybBatis的事務管理器的配置
- JPA的事務管理器的配置
- Hibernate的事務管理器的配置
- JTA 的事務管理器的配置
- 事務同步管理器
- 事務的傳播行為
- 示例
- 編程式的事務管理
- 示例
概述
Spring為事務管理提供了一致的編程模板,在高層次建立了統(tǒng)一的事務抽象。也就是說,不管選擇Spring JDBC、Hibernate 、JPA 還是iBatis,Spring都讓我們可以用統(tǒng)一的編程模型進行事務管理。
類似Spring DAO 為不同的持久化技術實現(xiàn)提供了模板類一樣,Spring事務管理也提供了事務模板類TransactionTemplate。 通過TransactionTemplate并配合使用事務回調(diào)TransactionCallback指定具體的持久化操作,就可以 通過編程的方式實現(xiàn)事務管理,而無須關注資源獲取、復用、釋放、事務同步和異步處理等操作。
Spring事務管理的亮點在于聲明式事務管理,Spring允許通過聲明的方式,在IoC配置中指定事務的邊界和事務屬性,Spring會自動在指定的事務邊界上應用事務屬性。
事務管理關鍵抽象
在Spring事務管理SPI(Service Provider Interface)的抽象層主要包括3個接口,分別是PlatformTransactionManager、TransactionDefinition和TransactionStatus。 都在org.springframework.transaction包中。
TransactionDefinition用于描述事務的隔離級別、超時時間、是否為只讀事務和事務傳播規(guī)則等控制事務具體行為的事務屬性,這些事務屬性可以通過XML配置或注解描述提供,也可以通過手工編程的方式設置。
PlatformTransactionManager根據(jù)TransactionDefinition提供的事務屬性配置信息,創(chuàng)建事務,并用TransactionStatus描述這個激活事務的狀態(tài)。
Spring事務管理的實現(xiàn)類
spring將事務管理委托底層具體的持久化實現(xiàn)框架去完成,因此針對不同的框架spring有的不同的接口實現(xiàn)類.
| org.springframework.orm.jpa.JpaTransactionManager | 使用JPA進行持久化時,使用該事務管理器 |
| org.springframework.orm.hibernateX.HibernateTransactionManager | 使用HibernateX版本時使用該事務管理器 |
| org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用SpringJDBC或MyBatis等基于DataSource數(shù)據(jù)源的持久化技術時,使用該事務管理器 |
| org.springframework.orm.jdo.JdoTransactionManager | 使用JDO進行持久化時,使用該事務管理器 |
| org.springframework.transaction.jta.JtaTransactionManager | 具有多個數(shù)據(jù)源的全局事務使用該事務管理器(不管采用何種持久化技術) |
要實現(xiàn)事務管理,首先要在Spring中配置好相應的事務管理器,為事務管理器指定數(shù)據(jù)資源及一些其他事務管理控制屬性。
下面介紹一下幾個常見的事務管理器的配置
Spring JDBC 和MybBatis的事務管理器的配置
Spring JDBC 和MybBatis都是基于數(shù)據(jù)源的Connection訪問數(shù)據(jù)庫,所有都可以使用DataSourceTransactionManager, 配置如下
<!--引用外部的Properties文件--> <context:property-placeholder location="classpath:jdbc.properties"/><!--配置一個數(shù)據(jù)源--> <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}"/><!--基于數(shù)據(jù)源的事務管理器,通過屬性引用數(shù)據(jù)源--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"p:dataSource-ref="dataSource"/>JPA的事務管理器的配置
要配置一個JPA事務管理器,必須現(xiàn)提供一個DataSource,然后配置一個EntityManagerFactory,最后才配置JpaTransationManager.
.......<!--通過dataSource-ref指定一個數(shù)據(jù)源--> <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已經(jīng)取消了對Hibernate3.6之前的版本支持,并全面支持Hibernate5.0. 因此,只為Hibernate3.6+提供事務管理器。
以Hibernate4.0為例
.... <!--通過dataSource-ref引用數(shù)據(jù)源 和 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應用服務器容器中的數(shù)據(jù)源--> <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等訪問數(shù)據(jù)庫的連接或者會話對象統(tǒng)稱為資源,這些資源在同一時刻是不能多線程共享的。
為了讓DAO、Service類可能做到singleton, Spring的事務同步管理類org.springframework.transaction.support.TransactionSynchronizationManager使用ThreadLocal為不同事務線程提供了獨立的資源副本,同時維護事務配置的屬性和運行狀態(tài)信息。
事務同步管理器是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 |
這些工具類都提供了靜態(tài)的方法,通過這些方法可以獲取和當前線程綁定的資源,如
DataSourceUtils.getConnection (DataSource
dataSource)可以從指定的數(shù)據(jù)源中獲取和當前線程綁定的ConnectionHibernate的SessionFactoryUtils.getSession (SessionFactory
sessionFactory, boolean allowCreate)則從指定的SessionFactory中獲取和當前線程綁定的Session。當需要脫離模板類,手工操作底層持久技術的原生API時,就需要通過這些工具類獲取線程綁定的資源,而不應該直接從DataSource或SessionFactory中獲取。因為后者不能獲得和本線程相關的資源,因此無法讓數(shù)據(jù)操作參與到本線程相關的事務環(huán)境中。
這些工具類還有另外一個重要的用途:將特定異常轉(zhuǎn)換為Spring的DAO異常。
Spring為不同的持久化技術提供了模板類,模板類在內(nèi)部通過資源獲取工具類間接訪問TransactionSynchronizationManager中的線程綁定資源。所以,如果Dao使用模板類進行持久化操作,這些Dao就可以配置成singleton。如果不使用模板類,也可直接通過資源獲取工具類訪問線程相關的資源。
我們來開下TransactionSynchronizationManager的面紗:
TransactionSynchronizationManager將Dao、Service類中影響線程安全的所有“狀態(tài)”統(tǒng)一抽取到該類中,并用ThreadLocal進行替換,從此Dao(必須基于模板類或資源獲取工具類創(chuàng)建的Dao)和Service(必須采用Spring事務管理機制)摘掉了非線程安全的帽子,完成了脫胎換骨式的身份轉(zhuǎn)變。
事務的傳播行為
當我們調(diào)用一個基于Spring的Service接口方法(如UserService#addUser())時,它將運行于Spring管理的事務 環(huán)境中,Service接口方法可能會在內(nèi)部調(diào)用其它的Service接口方法以共同完成一個完整的業(yè)務操作,因此就會產(chǎn)生服務接口方法嵌套調(diào)用的情況, Spring通過事務傳播行為控制當前的事務如何傳播到被嵌套調(diào)用的目標服務接口方法中。
事務傳播是Spring進行事務管理的重要概念,其重要性怎么強調(diào)都不為過。但是事務傳播行為也是被誤解最多的地方,在本文里,我們將詳細分析不同事務傳播行為的表現(xiàn)形式,掌握它們之間的區(qū)別。
Spring在TransactionDefinition接口中規(guī)定了7種類型的事務傳播行為,它們規(guī)定了事務方法和事務方法發(fā)生嵌套調(diào)用時事務如何進行傳播:
| PROPAGATION_REQUIRED | 如果當前沒有事務,就新建一個事務,如果已經(jīng)存在一個事務中,加入到這個事務中。這是最常見的選擇 |
| PROPAGATION_SUPPORTS | 支持當前事務,如果當前沒有事務,就以非事務方式執(zhí)行。 |
| PROPAGATION_MANDATORY | 使用當前的事務,如果當前沒有事務,就拋出異常。 |
| PROPAGATION_REQUIRES_NEW | 新建事務,如果當前存在事務,把當前事務掛起 |
| PROPAGATION_NOT_SUPPORTED | 以非事務方式執(zhí)行操作,如果當前存在事務,就把當前事務掛起 |
| PROPAGATION_NEVER | 以非事務方式執(zhí)行,如果當前存在事務,則拋出異常。 |
| PROPAGATION_NESTED | 如果當前存在事務,則在嵌套事務內(nèi)執(zhí)行。如果當前沒有事務,則執(zhí)行與PROPAGATION_REQUIRED類似的操作。 |
當使用PROPAGATION_NESTED時,底層的數(shù)據(jù)源必須基于JDBC 3.0,并且實現(xiàn)者需要支持保存點事務機制。
示例
當服務接口方法分別使用表1中不同的事務傳播行為,且這些接口方法又發(fā)生相互調(diào)用的情況下,大部分組合都是一目了然,容易理解的。但是,也存在一些容易引起誤解的組合事務傳播方式。
下面,我們通過兩個具體的服務接口的組合調(diào)用行為來破解這一難點。這兩個服務接口分別是UserService和ForumService, UserSerice有一個addCredits()方法,ForumSerivce#addTopic()方法調(diào)用了 UserSerice#addCredits()方法,發(fā)生關聯(lián)性服務方法的調(diào)用:
@Service public class ForumService {private UserService userService;// ①調(diào)用其它服務接口的方法public void addTopic() {// ②被關聯(lián)調(diào)用的業(yè)務方法userService.addCredits();}public void setUserService(UserService userService) {this.userService = userService;}}嵌套調(diào)用的事務方法 : 對Spring事務傳播行為最常見的一個誤解是:當服務接口方法發(fā)生嵌套調(diào)用時,被調(diào)用的服務方法只能聲明為 PROPAGATION_NESTED。這種觀點犯了望文生義的錯誤,誤認為PROPAGATION_NESTED是專為方法嵌套準備的。這種誤解遺害不 淺,執(zhí)有這種誤解的開發(fā)者錯誤地認為:應盡量不讓Service類的業(yè)務方法發(fā)生相互的調(diào)用,Service類只能調(diào)用DAO層的DAO類,以避免產(chǎn)生嵌 套事務。
其實,這種顧慮是完全沒有必要的,PROPAGATION_REQUIRED已經(jīng)清楚地告訴我們:事務的方法會足夠“聰明”地判斷上下文是否已經(jīng)存在一個事務中,如果已經(jīng)存在,就加入到這個事務中,否則創(chuàng)建一個新的事務。
依照上面的例子,假設我們將ForumService#addTopic()和UserSerice#addCredits()方法的事務傳播行為都設置為PROPAGATION_REQUIRED,這兩個方法將運行于同一個事務中。
將ForumService#addTopic()設置為PROPAGATION_REQUIRED時, UserSerice#addCredits()設置為PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、 PROPAGATION_MANDATORY時,運行的效果都是一致的(當然,如果單獨調(diào)用addCredits()就另當別論了)。
當addTopic()運行在一個事務下(如設置為PROPAGATION_REQUIRED),而addCredits()設置為 PROPAGATION_NESTED時,如果底層數(shù)據(jù)源支持保存點,Spring將為內(nèi)部的addCredits()方法產(chǎn)生的一個內(nèi)嵌的事務。如果 addCredits()對應的內(nèi)嵌事務執(zhí)行失敗,事務將回滾到addCredits()方法執(zhí)行前的點,并不會將整個事務回滾。內(nèi)嵌事務是內(nèi)層事務的一 部分,所以只有外層事務提交時,嵌套事務才能一并提交。
嵌套事務不能夠提交,它必須通過外層事務來完成提交的動作,外層事務的回滾也會造成內(nèi)部事務的回滾。
嵌套事務和新事務
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED也是容易混淆的兩個傳播行為。PROPAGATION_REQUIRES_NEW 啟動一個新的、和外層事務無關的“內(nèi)部”事務。該事務擁有自己的獨立隔離級別和鎖,不依賴于外部事務,獨立地提交和回滾。當內(nèi)部事務開始執(zhí)行時,外部事務 將被掛起,內(nèi)務事務結束時,外部事務才繼續(xù)執(zhí)行。
由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區(qū)別在于:
- PROPAGATION_REQUIRES_NEW 將創(chuàng)建一個全新的事務,它和外層事務沒有任何關系,
- 而 PROPAGATION_NESTED 將創(chuàng)建一個依賴于外層事務的子事務,當外層事務提交或回滾時,子事務也會連帶提交和回滾。
以下幾個問題值得注意:
1.當業(yè)務方法被設置為PROPAGATION_MANDATORY時,它就不能被非事務的業(yè)務方法調(diào)用。
如將ForumService#addTopic ()設置為PROPAGATION_MANDATORY,如果展現(xiàn)層的Action直接調(diào)用addTopic()方法,將引發(fā)一個異常。正確的情況是: addTopic()方法必須被另一個帶事務的業(yè)務方法調(diào)用(如ForumService#otherMethod())。所以 PROPAGATION_MANDATORY的方法一般都是被其它業(yè)務方法間接調(diào)用的。2 當業(yè)務方法被設置為PROPAGATION_NEVER時,它將不能被擁有事務的其它業(yè)務方法調(diào)用。
假設UserService#addCredits ()設置為PROPAGATION_NEVER,當ForumService# addTopic()擁有一個事務時,addCredits()方法將拋出異常。所以PROPAGATION_NEVER方法一般是被直接調(diào)用的。3 當方法被設置為PROPAGATION_NOT_SUPPORTED時,外層業(yè)務方法的事務會被掛起,當內(nèi)部方法運行完成后,外層方法的事務重新運行。如果外層方法沒有事務,直接運行,不需要做任何其它的事。
在Spring聲明式事務管理的配置中,事務傳播行為是最容易被誤解的配置項,原因在于事務傳播行為名稱(如 PROPAGATION_NESTED:嵌套式事務)和代碼結構的類似性上(業(yè)務類方法嵌套調(diào)用另一個業(yè)務類方法).
編程式的事務管理
在實際的應用中很少通過編程來進行事務管理,但是Spring還是為編程式事務管理提供了模板類 TransactionTemplate,以滿足一些特殊場合的要求。
TransactionTemplate是線程安全的,因此可以在多個類中共享TransactionTemplate實例進行事務管理。
TransactionTemplate主要有兩個方法:
public void setTransactionManager(PlatformTransactionManager transactionManager) 設置事務管理器
public <T> T execute(TransactionCallback<T> action) throws TransactionException 在TransactionCallback回調(diào)接口中定義需要以事務方式組織的數(shù)據(jù)訪問邏輯
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在一個事務中,第二條故意寫錯了表名,會執(zhí)行失敗,第一條已經(jīng)成功的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) {// 需要在事務中執(zhí)行的邏輯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注解的類自動轉(zhuǎn)化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文件中配置數(shù)據(jù)庫的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" /><!--基于數(shù)據(jù)源的事務管理器,通過屬性引用數(shù)據(jù)源--><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");}}運行結果
第二條因為執(zhí)行失敗,第一條也回滾了,未插入數(shù)據(jù), OK。
總結
以上是生活随笔為你收集整理的Spring JDBC-Spring对事务管理的支持的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring-AOP 混合使用各种切面类
- 下一篇: Spring4.X系列之IOC