您好,登錄后才能下訂單哦!
小編今天帶大家了解Spring的事務管理如何理解,文中知識點介紹的非常詳細。覺得有幫助的朋友可以跟著小編一起瀏覽文章的內容,希望能夠幫助更多想解決這個問題的朋友找到問題的答案,下面跟著小編一起深入學習“Spring的事務管理如何理解”的知識吧。
事務(Transaction),一般是指要做的或所做的事情。在計算機術語中是指訪問并可能更新數據庫中各種數據項的一個程序執行單元(unit)。
這里我們以取錢的例子來講解:比如你去ATM機取1000塊錢,大體有兩個步驟:第一步輸入密碼金額,銀行卡扣掉1000元錢;第二步從ATM出1000元錢。這兩個步驟必須是要么都執行要么都不執行。如果銀行卡扣除了1000塊但是ATM出錢失敗的話,你將會損失1000元;如果銀行卡扣錢失敗但是ATM卻出了1000塊,那么銀行將損失1000元。
如何保證這兩個步驟不會出現一個出現異常了,而另一個執行成功呢?事務就是用來解決這樣的問題。事務是一系列的動作,它們綜合在一起才是一個完整的工作單元,這些動作必須全部完成,如果有一個失敗的話,那么事務就會回滾到最開始的狀態,仿佛什么都沒發生過一樣。在企業級應用程序開發中,事務管理是必不可少的技術,用來確保數據的完整性和一致性。
①、原子性(Atomicity
):事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要么全部完成,要么完全不起作用。
②、一致性(Consistency
):一旦事務完成(不管成功還是失敗),系統必須確保它所建模的業務處于一致的狀態,而不會是部分完成部分失敗。在現實中的數據不應該被破壞。
③、隔離性(Isolation
):可能有許多事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。
④、持久性(Durability
):一旦事務完成,無論發生什么系統錯誤,它的結果都不應該受到影響,這樣就能從任何系統崩潰中恢復過來。通常情況下,事務的結果被寫到持久化存儲器中。
首先我們創建一個Java工程,然后導入 Spring 核心事務包
我們打開Spring的核心事務包,查看如下類:org.springframework.transaction
上面所示的三個類文件便是Spring的事務管理接口。如下圖所示:下面我們分別對這三個接口進行簡單的介紹
Spring事務管理器的接口是org.springframework.transaction.PlatformTransactionManager
,如上圖所示,Spring并不直接管理事務,通過這個接口,Spring為各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,也就是將事務管理的職責委托給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。
我們進入到 PlatformTransactionManager
接口,查看源碼:
①、TransactionStatus getTransaction(TransactionDefinition definition)
,事務管理器 通過TransactionDefinition
,獲得“事務狀態”,從而管理事務。
②、void commit(TransactionStatus status)
根據狀態提交
③、void rollback(TransactionStatus status)
根據狀態回滾
也就是說Spring事務管理的為不同的事務API提供一致的編程模型,具體的事務管理機制由對應各個平臺去實現。
比如下面我們導入實現事務管理的兩種平臺:JDBC和Hibernate
然后我們再次查看PlatformTransactionManager
接口,會發現它多了幾個實現類,如下:
在上面 PlatformTransactionManager
接口中,有如下方法:
這個方法返回的是 TransactionStatus對象,然后程序根據返回的對象來獲取事務狀態,然后進行相應的操作。
而 TransactionStatus 這個接口的內容如下:
這個接口描述的是一些處理事務提供簡單的控制事務執行和查詢事務狀態的方法,在回滾或提交的時候需要應用對應的事務狀態。
上面講到的事務管理器接口PlatformTransactionManager
通過getTransaction(TransactionDefinition definition)
方法來得到事務,這個方法里面的參數是TransactionDefinition
類,這個類就定義了一些基本的事務屬性。
那么什么是事務屬性呢?事務屬性可以理解成事務的一些基本配置,描述了事務策略如何應用到方法上。事務屬性包含了5個方面,如圖所示:
TransactionDefinition 接口方法如下:
當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。
Spring 定義了如下七中傳播行為,這里以A業務和B業務之間如何傳播事務為例說明:
①、PROPAGATION_REQUIRED
:required
, 必須。默認值,A如果有事務,B將使用該事務;如果A沒有事務,B將創建一個新的事務。
②、PROPAGATION_SUPPORTS
:supports
,支持。A如果有事務,B將使用該事務;如果A沒有事務,B將以非事務執行。
③、PROPAGATION_MANDATORY
:mandatory
,強制。A如果有事務,B將使用該事務;如果A沒有事務,B將拋異常。
④、PROPAGATION_REQUIRES_NEW
:requires_new
,必須新的。如果A有事務,將A的事務掛起,B創建一個新的事務;如果A沒有事務,B創建一個新的事務。
⑤、PROPAGATION_NOT_SUPPORTED
:not_supported
,不支持。如果A有事務,將A的事務掛起,B將以非事務執行;如果A沒有事務,B將以非事務執行。
⑥、PROPAGATION_NEVER
:never
,從不。如果A有事務,B將拋異常;如果A沒有事務,B將以非事務執行。
⑦、PROPAGATION_NESTED
:nested
,嵌套。A和B底層采用保存點機制,形成嵌套事務。
定義了一個事務可能受其他并發事務影響的程度。
并發事務引起的問題:
在典型的應用程序中,多個事務并發運行,經常會操作相同的數據來完成各自的任務。并發雖然是必須的,但可能會導致以下的問題。
①、臟讀(Dirty reads
)——臟讀發生在一個事務讀取了另一個事務改寫但尚未提交的數據時。如果改寫在稍后被回滾了,那么第一個事務獲取的數據就是無效的。
②、不可重復讀(Nonrepeatable read
)——不可重復讀發生在一個事務執行相同的查詢兩次或兩次以上,但是每次都得到不同的數據時。這通常是因為另一個并發事務在兩次查詢期間進行了更新。
③、幻讀(Phantom read
)——幻讀與不可重復讀類似。它發生在一個事務(T1)讀取了幾行數據,接著另一個并發事務(T2)插入了一些數據時。在隨后的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄。
注意:不可重復讀重點是修改,而幻讀重點是新增或刪除。
在 Spring 事務管理中,為我們定義了如下的隔離級別:
①、ISOLATION_DEFAULT:使用后端數據庫默認的隔離級別
②、ISOLATION_READ_UNCOMMITTED:最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致臟讀、幻讀或不可重復讀
③、ISOLATION_READ_COMMITTED:允許讀取并發事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生
④、ISOLATION_REPEATABLE_READ:對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生
⑤、ISOLATION_SERIALIZABLE:最高的隔離級別,完全服從ACID的隔離級別,確保阻止臟讀、不可重復讀以及幻讀,也是最慢的事務隔離級別,因為它通常是通過完全鎖定事務相關的數據庫表來實現的
上面定義的隔離級別,在 Spring 的TransactionDefinition.class
中也分別用常量 -1,0,1,2,4,8表示。
比如 ISOLATION_DEFAULT 的定義:
這是事務的第三個特性,是否為只讀事務。如果事務只對后端的數據庫進行該操作,數據庫可以利用事務的只讀特性來進行一些特定的優化。通過將事務設置為只讀,你就可以給數據庫一個機會,讓它應用它認為合適的優化措施。
為了使應用程序很好地運行,事務不能運行太長的時間。因為事務可能涉及對后端數據庫的鎖定,所以長時間的事務會不必要的占用數據庫資源。事務超時就是事務的一個定時器,在特定時間內事務如果沒有執行完畢,那么就會自動回滾,而不是一直等待其結束。
事務五邊形的最后一個方面是一組規則,這些規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有遇到運行期異常時才會回滾,而在遇到檢查型異常時不會回滾(這一行為與EJB的回滾行為是一致的) 。但是你可以聲明事務在遇到特定的檢查型異常時像遇到運行期異常那樣回滾。同樣,你還可以聲明事務遇到特定的異常不回滾,即使這些異常是運行期異常。
編程式事務處理:所謂編程式事務指的是通過編碼方式實現事務,允許用戶在代碼中精確定義事務的邊界。即類似于JDBC編程實現事務管理。管理使用TransactionTemplate
或者直接使用底層的PlatformTransactionManager
。對于編程式事務管理,spring推薦使用TransactionTemplate。
聲明式事務處理:管理建立在AOP之上的。其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況提交或者回滾事務。聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過基于@Transactional注解的方式),便可以將事務規則應用到業務邏輯中。
簡單地說,編程式事務侵入到了業務代碼里面,但是提供了更加詳細的事務管理;而聲明式事務由于基于AOP,所以既能起到事務管理的作用,又可以不影響業務代碼的具體實現。
我們還是以轉賬為實例。不用事務看如何實現轉賬。在數據庫中有如下表 account ,內容如下:
有兩個用戶 Tom 和 Marry 。他們初始賬戶余額都為 10000。這時候我們進行如下業務:Tom 向 Marry 轉賬 1000 塊。那么這在程序中可以分解為兩個步驟:
①、Tom 的賬戶余額 10000 減少 1000 塊,剩余 9000 塊。
②、Marry 的賬戶余額 10000 增加 1000 塊,變為 11000塊。
上面兩個步驟要么都執行成功,要么都不執行。我們通過 TransactionTemplate 編程式事務來控制:
(這里不用事務其實不需要這么多jar包,為了后面的講解需要,我們一次性導入所有的jar包)
AccountDao 接口:
package com.ys.dao; public interface AccountDao { /** * 匯款 * @param outer 匯款人 * @param money 匯款金額 */ public void out(String outer,int money); /** * 收款 * @param inner 收款人 * @param money 收款金額 */ public void in(String inner,int money); }
AccountDaoImpl 接口實現類
package com.ys.dao.impl; import org.springframework.jdbc.core.support.JdbcDaoSupport; import com.ys.dao.AccountDao; public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { /** * 根據用戶名減少賬戶金額 */ @Override public void out(String outer, int money) { this.getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer); } /** * 根據用戶名增加賬戶金額 */ @Override public void in(String inner, int money) { this.getJdbcTemplate().update("update account set money = money + ? where username = ?",money,inner); } }
AccountService 接口
package com.ys.service; public interface AccountService { /** * 轉賬 * @param outer 匯款人 * @param inner 收款人 * @param money 交易金額 */ public void transfer(String outer,String inner,int money); }
AccountServiceImpl 接口實現類
package com.ys.service.impl; import com.ys.dao.AccountDao; import com.ys.service.AccountService; public class AccountServiceImpl implements AccountService{ private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String outer, String inner, int money) { accountDao.out(outer, money); accountDao.in(inner, money); } }
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <bean id="accountDao" class="com.ys.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.ys.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> </beans>
public class TransactionTest { @Test public void testNoTransaction(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService account = (AccountService) context.getBean("accountService"); //Tom 向 Marry 轉賬1000 account.transfer("Tom", "Marry", 1000); } }
上面的結果和我們想的一樣,Tom 賬戶 money 減少了1000塊。而 Marry 賬戶金額增加了1000塊。
這時候問題來了,比如在 Tom 賬戶 money 減少了1000塊正常。而 Marry 賬戶金額增加時發生了異常,實際應用中比如斷電(這里我們人為構造除數不能為0的異常),如下:
那么這時候我們執行測試程序,很顯然會報錯,那么數據庫是什么情況呢?
數據庫account :
我們發現,程序執行報錯了,但是數據庫 Tom 賬戶金額依然減少了 1000 塊,但是 Marry 賬戶的金額卻沒有增加。這在實際應用中肯定是不允許的,那么如何解決呢?
上面轉賬的兩步操作中間發生了異常,但是第一步依然在數據庫中進行了增加操作。實際應用中不會允許這樣的情況發生,所以我們這里用事務來進行管理。
Dao 層不變,我們在 Service 層注入TransactionTemplate 模板,因為是用模板來管理事務,所以模板需要注入事務管理器 DataSourceTransactionManager
。而事務管理器說到底還是用底層的JDBC在管理,所以我們需要在事務管理器中注入 DataSource。這幾個步驟分別如下:
AccountServiceImpl 接口:
package com.ys.service.impl; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import com.ys.dao.AccountDao; import com.ys.service.AccountService; public class AccountServiceImpl implements AccountService{ private AccountDao accountDao; private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(final String outer,final String inner,final int money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus arg0) { accountDao.out(outer, money); //int i = 1/0; accountDao.in(inner, money); } }); } }
Spring 全局配置文件 applicationContext.xml:
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <bean id="accountDao" class="com.ys.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.ys.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> <property name="transactionTemplate" ref="transactionTemplate"></property> </bean> <!-- 創建模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="txManager"></property> </bean> <!-- 配置事務管理器 ,管理器需要事務,事務從Connection獲得,連接從連接池DataSource獲得 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
測試文件保持不變,可以分兩次測試,第一次兩次操作沒有發生異常,然后數據庫正常改變了。第二次操作中間發生了異常,發現數據庫內容沒變。
如果大家有興趣也可以試試底層的PlatformTransactionManager
來進行事務管理,我這里給出主要代碼:
//定義一個某個框架平臺的TransactionManager,如JDBC、Hibernate DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 設置數據源 DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定義事務屬性 transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 設置傳播行為屬性 TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 獲得事務狀態 try { // 數據庫操作 accountDao.out(outer, money); int i = 1/0; accountDao.in(inner, money); dataSourceTransactionManager.commit(status);// 提交 } catch (Exception e) { dataSourceTransactionManager.rollback(status);// 回滾 }
Dao 層和 Service 層與我們最先開始的不用事務實現轉賬保持不變。主要是 applicationContext.xml 文件變化了。
我們在 applicationContext.xml 文件中配置 aop 自動生成代理,進行事務管理:
①、配置管理器
②、配置事務詳情
③、配置 aop
<?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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <bean id="accountDao" class="com.ys.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.ys.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 1 事務管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2 事務詳情(事務通知) , 在aop篩選基礎上,比如對ABC三個確定使用什么樣的事務。例如:AC讀寫、B只讀 等 <tx:attributes> 用于配置事務詳情(屬性屬性) <tx:method name=""/> 詳情具體配置 propagation 傳播行為 , REQUIRED:必須;REQUIRES_NEW:必須是新的 isolation 隔離級別 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/> </tx:attributes> </tx:advice> <!-- 3 AOP編程,利用切入點表達式從目標類方法中 確定增強的連接器,從而獲得切入點 --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.ys.service..*.*(..))"/> </aop:config> </beans>
測試類這里我們就不描述了,也是分有異常和無異常進行測試,發現與預期結果是吻合的。
分為如下兩步:
①、在applicationContext.xml配置事務管理器,將并事務管理器交予spring
②、在目標類或目標方法添加注解即可 @Transactional
首先在 applicationContext.xml 文件中配置如下:
<!-- 1 事務管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2 將管理器交予spring * transaction-manager 配置事務管理器 * proxy-target-class true : 底層強制使用cglib 代理 --> <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
其次在目標類或者方法添加注解@Transactional。如果在類上添加,則說明類中的所有方法都添加事務,如果在方法上添加,則只有該方法具有事務。
package com.ys.service.impl; import javax.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.ys.dao.AccountDao; import com.ys.service.AccountService; @Transactional(propagation=Propagation.REQUIRED , isolation = Isolation.DEFAULT) @Service("accountService") public class AccountServiceImpl implements AccountService{ @Resource(name="accountDao") private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String outer, String inner, int money) { accountDao.out(outer, money); //int i = 1/0; accountDao.in(inner, money); } }
感謝大家的閱讀,以上就是“Spring的事務管理如何理解”的全部內容了,學會的朋友趕緊操作起來吧。相信億速云小編一定會給大家帶來更優質的文章。謝謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。