什麼是事務(Transaction)?事務是數據庫中的概念,是指訪問並可能更新數據庫中各類數據項的一個程序執行單元(unit)。java
有個很是經典的轉帳問題:A向B轉款1000元,A轉出成功,扣除了A帳戶的1000元,但當系統準備向B帳戶增長1000元出現了故障,轉入失敗,可是A帳戶的金額已經扣除,這樣的結果是A帳戶憑空少了1000元,很明顯這樣是不行的,正確的作法應該是當B帳戶增長成功後,A帳戶的扣款才能生效。mysql
簡單總結一句話就是:事務邏輯上的一組操做,組成這組操做的各個邏輯單元,要麼一塊兒成功,要麼一塊兒失敗web
原子性(Atomicity):事務是一個原子操做,由一系列動做組成。事務的原子性確保動做要麼所有完成,要麼徹底不起做用。spring
一致性(Consistency):一旦事務完成(無論成功仍是失敗),系統必須確保它所建模的業務處於一致的狀態,而不會是部分完成部分失敗。在現實中的數據不該該被破壞。sql
隔離性(Isolation):可能有許多事務會同時處理相同的數據,所以每一個事務都應該與其餘事務隔離開來,防止數據損壞。數據庫
持久性(Durability):一旦事務完成,不管發生什麼系統錯誤,它的結果都不該該受到影響,這樣就能從任何系統崩潰中恢復過來。一般狀況下,事務的結果被寫到持久化存儲器中。編程
事務隔離意味着對於某一個正在運行的事務來講,好像系統中只有這一個事務,其餘併發的事務都不存在同樣。大部分狀況下,不多使用徹底隔離的事務。但不徹底隔離的事務會帶來以下一些問題。mybatis
髒數據(Dirty Read):一個事務讀到了另外一個事務的未提交的數據併發
不可重讀(Unrepeatable Read):一個事務讀到了另外一個事務已經提交的 update 的數據致使屢次查詢結果不一致app
幻讀(Phantom Read):一個事務讀到了另外一個事務已經提交的 insert 的數據致使屢次查詢結果不一致
經過設置事務隔離級別解決這些讀的問題,事務隔離級別分別是:
讀操做未提交(Read Uncommitted):說明一個事務在提交前,其變化對於其餘事務來講是可見的。這樣髒讀、不可重讀和幻讀都是容許的。當一個事務已經寫入一行數據但未提交,其餘事務都不能再寫入此行數據;可是,任何事務均可以讀任何數據。這個隔離級別使用排寫鎖實現(髒讀,不可重複讀,虛讀都有可能發生)
讀操做已提交(Read Committed):它使用臨時的共讀鎖和排寫鎖實現。這種隔離級別不容許髒讀,但不可重讀和幻讀是容許的(避免髒讀。可是不可重複讀和虛讀有可能發生)
可重讀(Repeatable Read):說明事務保證可以再次讀取相同的數據而不會失敗。此隔離級別不容許髒讀和不可重讀,但幻讀會出現(避免髒讀和不可重複讀.可是虛讀有可能發生)
可串行化(Serializable):提供最嚴格的事務隔離。這個隔離級別不容許事務並行執行,只容許串行執行。這樣,髒讀、不可重讀或幻讀均可發生(避免以上全部讀問題)
Mysql 默認:可重複讀
Oracle 默認:讀已提交
PlatformTransactionManager主要有三個方法
TransactionStatus getTransaction(TransactionDefinition definition) ,事務管理器 經過TransactionDefinition,得到「事務狀態」,從而管理事務。
void commit(TransactionStatus status) 根據狀態提交
void rollback(TransactionStatus status) 根據狀態回滾
事務的狀態:
獲取事務時帶入的接口爲TransactionDefinition
隔離級別取值詳解:
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底層採用保存點機制,造成嵌套事務。
實現該接口的有DataSourceTransactionManager和HibernateTransitionmanager
spring的事務處理分爲編程式事務處理與聲明式事務處理
編程式事務處理:所謂編程式事務指的是經過編碼方式實現事務,容許用戶在代碼中精肯定義事務的邊界。即相似於JDBC編程實現事務管理。管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於編程式事務管理,spring推薦使用TransactionTemplate。
聲明式事務處理:管理創建在AOP之上的。其本質是對方法先後進行攔截,而後在目標方法開始以前建立或者加入一個事務,在執行完目標方法以後根據執行狀況提交或者回滾事務。聲明式事務最大的優勢就是不須要經過編程的方式管理事務,這樣就不須要在業務邏輯代碼中摻瑣事務管理的代碼,只需在配置文件中作相關的事務規則聲明(或經過基於@Transactional註解的方式),即可以將事務規則應用到業務邏輯中。
簡單地說,編程式事務侵入到了業務代碼裏面,可是提供了更加詳細的事務管理;而聲明式事務因爲基於AOP,因此既能起到事務管理的做用,又能夠不影響業務代碼的具體實現。
理論不少了,開始編碼吧,以轉帳做爲例子
DAO帳戶接口:
//帳戶接口 public interface AccountDao { //轉出 void out(String outer,Double money); //轉入 void in(String in,Double money); }
轉帳實現:
//轉帳實現 public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { public void out(String outer, Double money) { this.getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer); } public void in(String in, Double money) { this.getJdbcTemplate().update("update account set money = money + ? where username = ?",money,in); } }
Service接口:
public interface AccountService { void transfer(String from,String to,Double money); }
Service實現
public class AccountServiceImpl implements AccountService { // 業務層注入 DAO: private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public void transfer(String from, String to, Double money) { accountDao.out(from,money); accountDao.in(to,money); }
測試:
@Test public void fun1(){ ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml"); AccountService account = (AccountService) context.getBean("accountService"); //張三 向 李四 轉帳1000 account.transfer("zhangsan", "lisi", 1000d); }
數據庫原先數據:
執行方法以後:
這個結果很正常,如今咱們讓程序出一些錯誤,在兩個操做之間增長一個異常
accountDao.out(from,money); int i= 1/0;//異常操做 accountDao.in(to,money);
運行報錯:java.lang.ArithmeticException: / by zero
可是張三的帳戶仍是扣除了1000元,李四帳戶並未改動
如今須要咱們來改造代碼,讓轉帳支持事務,改造以下:
在AccountServiceImpl注入事務管理,代碼以下:
public class AccountServiceImpl implements AccountService { // 業務層注入 DAO: private AccountDao accountDao; //注入事務管理 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public void transfer(final String from,final String to, final Double money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus arg0) { accountDao.out(from, money); int i = 1/0; accountDao.in(to, 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.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="123456"></property> </bean> <bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.yuanqinnan.transaction.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>
測試方法不改,仍然報錯,可是張三的帳戶未被修改,說明事務生效
這種方式不修改原有的邏輯代碼,只是修改配置文件,配置文件以下:
<?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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.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="123456"></property> </bean> <bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.yuanqinnan.transaction.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.yuanqinnan.transaction.AccountServiceImpl.transfer(..))"/> </aop:config> </beans>
測試數據無誤
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" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="com.yuanqinnan.transaction" ></context:component-scan> <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="123456"></property> </bean> <bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.yuanqinnan.transaction.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 將管理器交予spring * transaction-manager 配置事務管理器 * proxy-target-class true : 底層強制使用cglib 代理 --> <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/> </beans>
AccountServiceImpl加上註解便可實現
@Transactional(propagation=Propagation.REQUIRED , isolation = Isolation.DEFAULT) public class AccountServiceImpl implements AccountService {}
以上三種方式都可實現事務的管理,事務管理講完以後整個spring的入門總結也結束了,下面開始mybatis之旅