1.事務介紹java
事務(Transaction):訪問並能更新數據庫中數據項的一個程序執行單元。mysql
事務是一系列的動做,它們綜合在一塊兒纔是一個完整的工做單元,這些動做必需要麼所有完成,要麼什麼都不作,若是有一個失敗了的話,那麼事務就會回滾(RollBack)到最開始的狀態,在企業級的應用程序中,事務管理是必不可少的,用來確保數據的完整性和一致性。spring
事務的特性(ACID)sql
舉個例子,也是後面實現過程當中會一直用的例子:
1.Tom和Marry在某銀行都有帳戶,且他們每一個人的帳戶中都有1000元數據庫
2.Tom要向Marry匯款100元apache
那麼這個匯款的執行能夠分紅下面幾步:編程
1.檢測Tom的帳戶裏面有沒有100塊錢,若是有則容許匯款,若是沒有則不容許匯款後端
2.Tom的銀行帳戶,帳戶餘額減去100元服務器
3.Marry的銀行帳戶,帳戶餘額加上100元併發
那麼問題來了,在2步和3步之間,可能會產生故障或者異常,最low的例子是2步完成以後,銀行的服務器斷電了。那麼這個時候會不會出現Tom的銀行帳戶少了100元,而Marry的銀行帳戶卻沒有增長100元呢?
這個問題能夠先說下:若是沒有使用事務管理,這種狀況確實是存在的。
那麼如何模擬服務器斷電這種異常呢:那就是在2步和3步之間加入一個異常(除零異常就能夠)
Spring的事務管理的核心接口:
接着打開spring-tx包,能夠繼續查看org-springframework.transaction包
TransactionDefinition PlatformTransactionManager TransactionStatus是Spring事務管理的三個頂級接口,下面咱們用圖來解釋下這三個接口之間的關係:
PlatformTransaction事務管理器
Spring事務管理器的接口是org.springframework.transaction.PlatformTransactionManager,如上圖所示Spring並非直接管理事務,經過PlatformTransaction這個接口,Spring爲各個平臺如JDBC,Hibernate等提供對應的事務管理。也就是將事務管理的職責委託給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務實現。
進入到PlatformTransactionManager接口,查看源碼:
也就是說Spring事務管理是爲不一樣的事務API提供統一的編程模型,具體的事務管理機制由對應的各個平臺去實現:
有jdbc,orm平臺:
TransactionDefinition基本事務屬性定義
事務屬性能夠理解成事務的一些基本配置,描述了事務策略如何應用到方法上。
事務屬性:傳播行爲,隔離規則,是否只讀,事務超時,回滾規則。
TransactionDefinition接口定義的方法以下:
事務的傳播行爲和隔離級別的具體定義:
// Compiled from TransactionDefinition.java (version 1.6 : 50.0, no super bit) public abstract interface org.springframework.transaction.TransactionDefinition { // Field descriptor #4 I public static final int PROPAGATION_REQUIRED = 0; // Field descriptor #4 I public static final int PROPAGATION_SUPPORTS = 1; // Field descriptor #4 I public static final int PROPAGATION_MANDATORY = 2; // Field descriptor #4 I public static final int PROPAGATION_REQUIRES_NEW = 3; // Field descriptor #4 I public static final int PROPAGATION_NOT_SUPPORTED = 4; // Field descriptor #4 I public static final int PROPAGATION_NEVER = 5; // Field descriptor #4 I public static final int PROPAGATION_NESTED = 6; // Field descriptor #4 I public static final int ISOLATION_DEFAULT = -1; // Field descriptor #4 I public static final int ISOLATION_READ_UNCOMMITTED = 1; // Field descriptor #4 I public static final int ISOLATION_READ_COMMITTED = 2; // Field descriptor #4 I public static final int ISOLATION_REPEATABLE_READ = 4; // Field descriptor #4 I public static final int ISOLATION_SERIALIZABLE = 8;
具體解釋下傳播行爲:傳播行爲指的是當事務被另外一個事務調用時,必須指定事務如何傳播的。可能這麼說仍是有點不清晰,那麼能夠看具體定義的幾種傳播行爲的具體含義
隔離級別:定義了一個事務可能受其餘併發事務影響的程度
併發事務引發的問題:
不可重複的重點是「修改」,而幻讀的重點是「新增」
在Spring的事務管理中,定義了以下隔離級別:
只讀
這是事務的第三個特性,是否將事務設置爲只讀。數據庫能夠利用事務的只讀屬性來進行一些優化。經過將事務設置成只讀,你就能夠給數據庫一個機會,讓它應用它認爲合適的優化措施。
事務超時
爲了使應用更好的運行,事務不能運行太長時間,由於事務可能涉及對後端數據庫的鎖定,因此很長時間的事務會沒必要要的佔用數據庫資源,事務超時就是事務的一個定時器,在特定的時間內若是事務沒有執行完畢,那麼就會自動回滾,而不是一直等待事務結束。
回滾規則
回滾規則定義了哪些異常會致使事務回滾而哪些異常不會致使回滾。默認狀況下,事務遇到運行期異常時纔會回滾,在遇到檢查型異常時不會回滾。可是這些都是能夠設置的。好比你能夠設置檢查型異常也進行事務回滾。
Spring編程式事務和聲明式事務
編程式事務:所謂編程式事務就是指經過編碼方式實現事務,容許用戶在代碼中精肯定義事務的邊界。即相似JDBC編程實現事務管理。管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於編程式事務,推薦使用TransactionTemplate。
聲明式事務管理:管理創建在AOP上,其本質是對方法先後進行攔截,而後再目標方法開始以前建立或加入一個事務,在執行完目標方法以後根據執行狀況提交或回滾事務。聲明式事務最大的優勢就是不須要經過編程的凡是管理事務,這樣就不須要在業務邏輯代碼中摻瑣事務管理的代碼,只須要在配置文件中作相關的事務規則聲明(或者基於註解@Transaction的形式)。
實驗
不用事務實現轉帳
數據庫中有以下表:
package com.fpc.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 ); }
第二步:編寫Dao層接口的實現:AccountDaoImpl:
package com.fpc.DaoImpl; import org.aspectj.weaver.patterns.ThisOrTargetAnnotationPointcut; import org.springframework.jdbc.core.support.JdbcDaoSupport; import com.fpc.Dao.AccountDao; public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public void out(String outer, int money) { // TODO 自動生成的方法存根 this.getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer); } @Override public void in(String inner, int money) { // TODO 自動生成的方法存根 this.getJdbcTemplate().update("update account set money = money + ? where username = ?",money,inner); } }
第三步:實現Service層 IAccountService
package com.fpc.Service; public interface IAccountService { /* * 轉帳 * @param outer 匯款人 * @param inner 收款人 * @param money 交易金額 * */ public void transfer( String outer , String inner , int money ); }
第四步:Service的具體實現 AccountServiceImpl
package com.fpc.ServiceImpl; import org.apache.shiro.authc.Account; import com.fpc.Dao.AccountDao; import com.fpc.Service.IAccountService; public class AccountServiceImpl implements IAccountService{ private AccountDao accounDao; public void setAccounDao(AccountDao accounDao) { this.accounDao = accounDao; } @Override public void transfer(String outer, String inner, int money) { // TODO 自動生成的方法存根 accounDao.out(outer, money); accounDao.in(inner, money); } }
第五步:配置applicationContext文件:
<bean id="accountDao" class="com.fpc.DaoImpl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.fpc.ServiceImpl.AccountServiceImpl"> <property name="accounDao" ref="accountDao"></property> </bean>
第六步:編寫單元測試類
package com.fpc.UnitTest; import org.apache.catalina.core.ApplicationContext; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.apple.eawt.Application; import com.fpc.Service.IAccountService; import com.fpc.ServiceImpl.AccountServiceImpl; public class TransactionTest { @Test public void TestNoTransaction(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); IAccountService accountService = (IAccountService) context.getBean("accountService"); //Tom 向 Marry轉帳100元 accountService.transfer("Tom", "Marry", 100); } }
運行查看數據庫中的結果:
可見正常狀況下,轉帳是能夠成功的。
下面模擬異常狀況:
那麼這個時候咱們執行單元測試程序,很顯然會報「除零異常」
此時再去查看數據庫中該表:
咱們發現,Tom的帳戶中再次減小了100元,而Marray的帳戶中卻沒有增長100元。這在實際應用中確定是不被容許的。
那麼怎麼去解決這個問題呢?確定是引入事務管理
Dao層不變,咱們在Service層注入TransactionTemplate模板,由於是用模板來管理事務,因此模板須要注入事務管理器。DatasourceTransactionManager,而事務管理器說到底層是JDBC在管理,因此咱們須要在DatasourceTransactionManager中注入DataSource。
AccountServiceImpl:
package com.fpc.ServiceImpl; import org.apache.shiro.authc.Account; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import com.fpc.Dao.AccountDao; import com.fpc.Service.IAccountService; public class AccountServiceImpl implements IAccountService{ private AccountDao accounDao; private TransactionTemplate transactionTemplate; public void setAccounDao(AccountDao accounDao) { this.accounDao = accounDao; } public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } @Override public void transfer(String outer, String inner, int money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus arg0) { // TODO 自動生成的方法存根 accounDao.out(outer, money); int i = 1/0; accounDao.in(inner, money); } }); }
更改配置文件:
<!-- 事務管理,transaction manager,user JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountDao" class="com.fpc.DaoImpl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.fpc.ServiceImpl.AccountServiceImpl"> <property name="accounDao" ref="accountDao"></property> <property name="transactionTemplate" ref="transactionTemplate"></property> </bean> <!-- 建立模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"></property> </bean> <!-- 配置事物管理器,管理器須要事務,事務從Connection得到,鏈接從鏈接池得到 --> <!-- transactionManager配置在上面 -->
mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 800 | | 2 | Marry | 1100 | +----+----------+-------+
mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 700 | | 2 | Marry | 1200 | +----+----------+-------+ 2 rows in set (0.00 sec)
mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 700 | | 2 | Marry | 1200 | +----+----------+-------+ 2 rows in set (0.00 sec)
聲明式事務處理實現(AOP)
DAO層和Service不須要改變,主要是applicationContext.xml文件發生了變化。<!-- 事務管理,transaction manager,user JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事務詳情 :在AOP篩選基礎上,好比對ABC三個肯定使用什麼樣的事物,例如AC讀寫,B只讀等 <tx:attributes>用於配置事物詳情(屬性) <tx:method name=""/>詳情具體配置 propagation:傳播行爲,REQUIRED:必須,REQUIRED_NEW:必須是新的,isolation:隔離級別 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/> </tx:attributes> </tx:advice> <!-- AOP切面編程,利用切入點表達式從目標類方法中,肯定加強的鏈接器,從而得到切入點 --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.fpc.ServiceImpl..*.*(..))"/> </aop:config> <bean id="accountDao" class="com.fpc.DaoImpl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.fpc.ServiceImpl.AccountServiceImpl"> <property name="accounDao" ref="accountDao"></property> <!-- <property name="transactionTemplate" ref="transactionTemplate"></property> --> </bean> <!-- 建立模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"></property> </bean> <!-- 配置事物管理器,管理器須要事務,事務從Connection得到,鏈接從鏈接池得到 --> <!-- transactionManager配置在上面 --> </beans>
package com.fpc.ServiceImpl; import org.apache.shiro.authc.Account; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import com.fpc.Dao.AccountDao; import com.fpc.Service.IAccountService; @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT) public class AccountServiceImpl implements IAccountService{ private AccountDao accounDao; public void setAccounDao(AccountDao accounDao) { this.accounDao = accounDao; } @Override public void transfer(String outer, String inner, int money) { accounDao.out(outer, money); int i = 1/0; accounDao.in(inner, money); } }
mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 700 | | 2 | Marry | 1200 | +----+----------+-------+ 2 rows in set (0.00 sec) mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 600 | | 2 | Marry | 1300 | +----+----------+-------+ 2 rows in set (0.00 sec) mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 600 | | 2 | Marry | 1300 | +----+----------+-------+ 2 rows in set (0.00 sec)