事務管理對於企業應用而言是很是重要的,事務的存在保證了用戶的每一次操做都是可靠的,當用戶操做出現異常時也不至於破壞了後臺的數據。例如銀行的自動取款機,萬一你在轉帳的時候出現了異常,事務機制會保證你後臺的數據仍是出異常操做以前的數據,也就是是你出異常的這些操做失效。java
事務就是一組因爲邏輯上緊密關聯而合併成一個總體(工做單元)的多個數據庫操做,這些操做要麼都執行,要麼都不執行。spring
銀行轉帳操做:開啓事務,就是保證轉帳的操做要麼都執行,要麼都不執行。sql
若是在你的帳戶減去轉帳金額後出現異常,不能只是你的帳戶扣錢,對方帳戶加錢,這不就賠大了嗎;若是此處出現異常,由於該事務的操做並無全執行完,事務就會回退到轉帳操做前,也就是「本身帳戶減去 轉帳金額」的這個操做失效,不會執行了,這就保證了你帳戶的數據不被破壞,等系統好了你就能夠再次執行轉帳操做了。數據庫
從上面的例子咱們看出:事務就必須確保出故障前對帳戶的操做不生效,就像用戶剛纔徹底沒有使用過取款機同樣,以保證用戶和銀行的利益都不受損失。express
原子性(atomicity):事務的原子性表現爲一個事務所涉及到的多個操做是捆綁在一塊兒的,要求事務中的全部操做要麼都執行,要麼都不執行,就像上面例子中的轉帳操做。編程
一致性(consistency):一個事務中無論涉及到多少個操做,都必須保證事務執行以前數據是正確的,事務執行以後數據也是正確,若是在事務執行過程當中,存在操做失敗的操做則其餘全部操做都必須撤銷,將數據恢復到事務執行以前的狀態,這個過程叫「回滾」。併發
隔離性(isolation):要求多個事務在併發執行過程當中不會互相干擾。在應用程序實際執行的過程當中,事務每每是兵法執行的,因此極可能許多事務會同時處理相同的數據,所以每一個事務都應該與其餘事務分開執行,否則同一個數據同時在多個事務中都被修改,那這個數據不就亂了嗎?框架
持久性(durability):持久性是事務執行完成後,他對數據的修改是永久保存的,不會由於時間長,或者出現故障而受影響。也就是說並不能你執行轉帳操做後過了一段時間後你轉出去的金額本身又回到你的帳戶,也就想一想吧。ide
當在一個事務中執行多個操做時,要麼全部的事務都被提交(commit),要麼整個事務回滾(rollback)到最初狀態
在JDBC中,事務默認是自動提交的,每次執行一個 SQL 語句時,若是執行成功,就會向數據庫自動提交,而不能回滾
爲了讓多個 SQL 語句做爲一個事務執行:
調用 Connection 對象的 setAutoCommit(false); 以取消自動提交事務
在全部的 SQL 語句都成功執行後,調用 commit(); 方法提交事務
在出現異常時,調用 rollback(); 方法回滾事務
能夠經過Connection的getAutoCommit()方法來得到當前事務的提交方式性能
package cn.itcast.cd; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.junit.Test; import cn.itcast.cd.utils.DbUtils; public class TestTransaction { /* * 需求:張三給李四轉帳1000元 * * 轉帳成功的條件: * 一、判斷張三的帳號餘額是否>=1000 * 二、從張三的帳號中轉出1000. * 三、給李四的帳號中轉入1000. */ @Test public void testTrans(){ Connection connection = DbUtils.getConnection(); try { //開啓事務,默認的狀況下是true, 只要執行sql就開啓事務物,執行完sql後就提交事務connection.setAutoCommit(false);
PreparedStatement preparedStatement = null; ResultSet resultSet = null; String sql = ""; try { sql = "select * from accounts where name=?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, "張三"); resultSet = preparedStatement.executeQuery(); if (resultSet.next()){ Double sum = resultSet.getDouble("sum"); //判斷餘額 if (sum < 1000){ System.out.println("餘額不足,操做失敗!"); return; } //轉出 sql = "update accounts set sum=sum-? where name=?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1, 1000); preparedStatement.setString(2, "張三"); preparedStatement.executeUpdate(); //建立異常,沒有事務則轉出了1000,發生異常,轉入失敗,整個轉帳操做失敗! int a = 2/0; //轉入 sql = "update accounts set sum=sum+? where name=?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1, 1000); preparedStatement.setString(2, "李四"); preparedStatement.executeUpdate(); //提交事務,代表整個轉帳操做成功,將修改的數據更新到數據庫中, 事務就又變成了自動提交. connection.commit(); } } catch (SQLException e) { e.printStackTrace(); } finally{ DbUtils.close(connection, preparedStatement, resultSet); } } catch (Exception e) { e.printStackTrace(); //發生異常,事務回滾到原始狀態. try { connection.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } } }
主要有兩種方式實現,一種是在代碼中編寫(編程式事務管理),一種是聲明式事務(又可分爲AOP式事務管理和註解式事務管理)。在代碼中編寫要更加細粒度,而不少時候咱們只須要簡單的事務處理,那就能夠用聲明式事務。
從圖中能夠看出,spring並不直接管理事務,而是提供了一個事務接口,實現事務的具體方法是由底層本身自己決定,若是管理JDBC DataSource事務就使用DataSourceTransactionManager事務管理器,若是管理Hibernate事務,則使用HibernateTransactionManager事務管理器;也就是說Spring管理機制是一種策略模式,他只提供接口,用哪一種具體的事務管理器由持久層的實現框架決定。
編程式事務就是用編碼的方式實現事務,比較相似於JDBC編程實現事務。
spring實現編程式事務的方式方式中提供了一種模板類,可以幫助咱們實現DataSourceTransactionManager管理事務,這中模板相似於JDBCTemplate模板,將數據操做封裝在模板類中。
實例:實現帳號
1)建立數據庫
2)配置好spring環境
<!-- 引入外部屬性文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置C3p0 --> <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> </bean> <!-- 配置業務層類 --> <bean id="accountService" class="com.neuedu.spring.demo1.AccountService"> <property name="accountDao" ref="accountDao"></property> <!-- 注入事務管理的模板類 --> <property name="atTemplate" ref="transactionTemplate"></property> </bean> <!-- 配置DAO類 --> <bean id="accountDao" class="com.neuedu.spring.demo1.AccountDaoImpl"> <!-- 注入鏈接池 --> <property name="dataSource" ref="comboPooledDataSource"></property> </bean> <!-- 配置事務管理器 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="comboPooledDataSource"></property> </bean> <!-- 配置事務管理的模板:spring爲了簡化事務管理代碼提供的模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="dataSourceTransactionManager"></property> </bean>
3)DAO層實現類
package com.neuedu.spring.demo1; import org.springframework.jdbc.core.support.JdbcDaoSupport; /* * 項目名稱:spring-BankAccount * @author:wzc * @date 建立時間:2017年9月12日 下午5:48:06 * @Description:實現轉帳DAO * @parameter * */ public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { /*Method:outMoney *Description:實現從數據庫中將帳戶轉帳金額扣除 *param:out,轉帳帳戶 *param:money,轉帳金額 */ @Override public void outMoney(String out, Double money) { String sql="update t_account set salary=salary-? where name=?"; this.getJdbcTemplate().update(sql, money,out); } /*Method:inMoney *Description:實現從數據庫中向帳戶轉入轉帳金額 *param:in,收款帳戶 *param:money,轉帳金額 */ @Override public void inMoney(String in, Double money) { String sql="update t_account set salary=salary+? where name=?"; this.getJdbcTemplate().update(sql, money,in); } }
4)業務層實現類
package com.neuedu.spring.demo1; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; /* * 項目名稱:spring-BankAccount * @author:wzc * @date 建立時間:2017年9月12日 下午5:39:04 * @Description:轉帳業務層實現 * @parameter * */ public class AccountService implements AccountServiceIn{ //注入accountDao private AccountDaoImpl accountDao; public void setAccountDao(AccountDaoImpl accountDao) { this.accountDao = accountDao; } //注入事務管理的模板 private TransactionTemplate atTemplate; public void setAtTemplate(TransactionTemplate atTemplate) { this.atTemplate = atTemplate; } /*Method:transfer *Description:實現轉帳操做 *param:out ,轉出帳戶 *param:in,轉入帳戶 *param:money 轉帳金額 */ @Override public void transfer(String out, String in, Double money) { atTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { //調用DAO層實現具體轉帳操做 accountDao.outMoney(out, 200d); //人爲製造Debug //int i=10/0; accountDao.inMoney(in, 200d); } }); } }
測試:
@Test public void test() { ApplicationContext ioc=new ClassPathXmlApplicationContext("spring.xml"); AccountService aService = ioc.getBean(AccountService.class); aService.transfer("孫悟空", "豬八戒", 200d); }
結果:
如今給轉帳操做人爲製做bug,在測試一下事務是否正常。
(一)基本原理:AOP
[1]前置通知:開啓事務
[2]返回通知:提交事務
[3]異常通知:回滾事務
[4]後置通知:釋放資源
(二)事務的傳播行爲
當事務方法被另外一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啓一個新事務,並在本身的事務中運行。事務的傳播行爲能夠由傳播屬性指定,在TransactionDefinition定義了7種類傳播行爲。
(三)事務的隔離級別
數據庫事務併發存在的問題
假設如今有兩個事務:Transaction01和Transaction02併發執行。
①髒讀
[1]Transaction01將某條記錄的AGE值從20修改成30。
[2]Transaction02讀取了Transaction01更新後的值:30。
[3]Transaction01回滾,AGE值恢復到了20。
[4]Transaction02讀取到的30就是一個無效的值。
②不可重複讀
[1]Transaction01讀取了AGE值爲20。
[2]Transaction02將AGE值修改成30。
[3]Transaction01再次讀取AGE值爲30,和第一次讀取不一致。
③幻讀
[1]Transaction01讀取了STUDENT表中的一部分數據。
[2]Transaction02向STUDENT表中插入了新的行。
[3]Transaction01讀取了STUDENT表時,多出了一些行。
數據庫系統必須具備隔離併發運行各個事務的能力,使它們不會相互影響,避免各類併發問題。一個事務與其餘事務隔離的程度稱爲隔離級別。SQL標準中規定了多種事務隔離級別,不一樣隔離級別對應不一樣的干擾程度,隔離級別越高,數據一致性就越好,但併發性越弱。
TransactionDefinition 接口中定義了五個表示隔離級別的常量,其中DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,一般這值就是READ_COMMITTED。如下是其餘四種的具體狀況:
①讀未提交:READ UNCOMMITTED
容許Transaction01讀取Transaction02未提交的修改。
②讀已提交:READ COMMITTED
要求Transaction01只能讀取Transaction02已提交的修改。
③可重複讀:REPEATABLE READ
確保Transaction01能夠屢次從一個字段中讀取到相同的值,即Transaction01執行期間禁止其它事務對這個字段進行更新。
④串行化:SERIALIZABLE
確保Transaction01能夠屢次從一個表中讀取到相同的行,在Transaction01執行期間,禁止其它事務對這個表進行添加、更新、刪除操做。能夠避免任何併發問題,但性能十分低下
各個隔離級別解決併發問題的能力見下表
(四)事務超時
所謂事務超時,就是指一個事務所容許執行的最長時間,若是超過該時間限制但事務尚未完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。
默認設置爲底層事務系統的超時值,若是底層數據庫事務系統沒有設置超時值,那麼就是none,沒有超時限制。
(五)事務只讀屬性
只讀事務屬性: 表示這個事務只讀取數據但不更新數據, 這樣能夠幫助數據庫引擎優化事務。
聲明式事務是創建在AOP之上的。其本質是對方法先後進行攔截,而後在目標方法開始以前建立或者加入一個事務,在執行完目標方法以後根據執行狀況提交或者回滾事務。聲明式事務最大的優勢就是不須要經過編程的方式管理事務,這樣就不須要在業務邏輯代碼中摻瑣事務管理的代碼,只需在配置文件中作相關的事務規則聲明(或經過基於@Transactional註解的方式),即可以將事務規則應用到業務邏輯中。
1)數據庫扔用上面的數據庫;
2)配置spring
<!-- 配置業務層類 --> <bean id="accountService" class="com.neuedu.spring.demo2.AccountService"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 配置DAO類 --> <bean id="accountDao" class="com.neuedu.spring.demo2.AccountDaoImpl"> <!-- 注入鏈接池 --> <property name="dataSource" ref="comboPooledDataSource"></property> </bean><!-- 配置事務管理器 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="comboPooledDataSource"></property> </bean> <!-- 開啓基於註解的事務管理 --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
3)DAO層不變
package com.neuedu.spring.demo2; import org.springframework.jdbc.core.support.JdbcDaoSupport; /* * 項目名稱:spring-BankAccount * @author:wzc * @date 建立時間:2017年9月12日 下午5:48:06 * @Description:實現轉帳DAO * @parameter * */ public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { /*Method:outMoney *Description:實現從數據庫中將帳戶轉帳金額扣除 *param:out,轉帳帳戶 *param:money,轉帳金額 */ @Override public void outMoney(String out, Double money) { String sql="update t_account set salary=salary-? where name=?"; this.getJdbcTemplate().update(sql, money,out); } /*Method:inMoney *Description:實現從數據庫中向帳戶轉入轉帳金額 *param:in,收款帳戶 *param:money,轉帳金額 */ @Override public void inMoney(String in, Double money) { String sql="update t_account set salary=salary+? where name=?"; this.getJdbcTemplate().update(sql, money,in); } }
4)在業務層service類上加入註解
package com.neuedu.spring.demo2; import org.springframework.transaction.annotation.Transactional; /* * 項目名稱:spring-BankAccount * @author:wzc * @date 建立時間:2017年9月12日 下午5:39:04 * @Description:轉帳業務層實現 * @parameter * */ @Transactional() public class AccountService implements AccountServiceIn{ //注入accountDao private AccountDaoImpl accountDao; public void setAccountDao(AccountDaoImpl accountDao) { this.accountDao = accountDao; } /*Method:transfer *Description:實現轉帳操做 *param:out ,轉出帳戶 *param:in,轉入帳戶 *param:money 轉帳金額 */ @Override public void transfer(String out, String in, Double money) { //調用DAO層實現具體轉帳操做 accountDao.outMoney(out, 200d); //人爲製造Debug //int i=10/0; accountDao.inMoney(in, 200d); } }
由於剛纔正常執行後悟空八戒分別是800,1200,接着執行異常狀況,我並無恢復數據庫數據,執行結果是800,1200,說明由於異常沒有再轉帳。
基於AspectJ的XML聲明式事務,實際就是AOP的XML實現方式。
spring配置:
<!-- 引入外部屬性文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置C3p0 --> <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> </bean> <!-- 配置業務層類 --> <bean id="accountService" class="com.neuedu.spring.demo3.AccountService"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 配置DAO類 --> <bean id="accountDao" class="com.neuedu.spring.demo3.AccountDaoImpl"> <!-- 注入鏈接池 --> <property name="dataSource" ref="comboPooledDataSource"></property> </bean><!-- 配置事務管理器 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="comboPooledDataSource"></property> </bean> <!-- 配置事務的通知,由於事務是創建在AOP上的 --> <tx:advice id="txAdivice" transaction-manager="dataSourceTransactionManager"> <tx:attributes> <!-- 配置事務管理的方法 --> <tx:method name="transfer" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 配置切面 --> <aop:config> <!-- 配置切入點表達式 --> <aop:pointcut expression="execution(* com.neuedu.spring.demo3.AccountService+.*(..))" id="pointcut"/> <!-- 配置切面 --> <aop:advisor advice-ref="txAdivice" pointcut-ref="pointcut"/> </aop:config>
而後DAO,Service都是業務正常的代碼,裏面沒有關於事務的任何代碼,顯然聲明式事務管理要優於編程式事務管理,這正是spring倡導的非侵入式的開發方式,使業務代碼不受污。
dao
package com.neuedu.spring.demo3; import org.springframework.jdbc.core.support.JdbcDaoSupport; /* * 項目名稱:spring-BankAccount * @author:wzc * @date 建立時間:2017年9月12日 下午5:48:06 * @Description:實現轉帳DAO * @parameter * */ public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { /*Method:outMoney *Description:實現從數據庫中將帳戶轉帳金額扣除 *param:out,轉帳帳戶 *param:money,轉帳金額 */ @Override public void outMoney(String out, Double money) { String sql="update t_account set salary=salary-? where name=?"; this.getJdbcTemplate().update(sql, money,out); } /*Method:inMoney *Description:實現從數據庫中向帳戶轉入轉帳金額 *param:in,收款帳戶 *param:money,轉帳金額 */ @Override public void inMoney(String in, Double money) { String sql="update t_account set salary=salary+? where name=?"; this.getJdbcTemplate().update(sql, money,in); } }
service
package com.neuedu.spring.demo3; import org.springframework.transaction.annotation.Transactional; /* * 項目名稱:spring-BankAccount * @author:wzc * @date 建立時間:2017年9月12日 下午5:39:04 * @Description:轉帳業務層實現 * @parameter * */ public class AccountService implements AccountServiceIn{ //注入accountDao private AccountDaoImpl accountDao; public void setAccountDao(AccountDaoImpl accountDao) { this.accountDao = accountDao; } /*Method:transfer *Description:實現轉帳操做 *param:out ,轉出帳戶 *param:in,轉入帳戶 *param:money 轉帳金額 */ @Override public void transfer(String out, String in, Double money) { //調用DAO層實現具體轉帳操做 accountDao.outMoney(out, 200d); //人爲製造Debug //int i=10/0; accountDao.inMoney(in, 200d); } }
而此時如何配置事務屬性呢?在配置事務管理方法的時候能夠添加事務的屬性。