一、
什么是事務(Transaction)?事務是數據庫中的概念,是指訪問並可能更新數據庫中各種數據項的一個程序執行單元(unit)。
有個非常經典的轉賬問題:A向B轉款1000元,A轉出成功,扣除了A賬戶的1000元,但當系統准備向B賬戶增加1000元出現了故障,轉入失敗,但是A賬戶的金額已經扣除,這樣的結果是A賬戶憑空少了1000元,很明顯這樣是不行的,正確的做法應該是當B賬戶增加成功后,A賬戶的扣款才能生效。
簡單總結一句話就是:事務邏輯上的一組操作,組成這組操作的各個邏輯單元,要么一起成功,要么一起失敗
二、
-
原子性(Atomicity):事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要么全部完成,要么完全不起作用。
-
一致性(Consistency):一旦事務完成(不管成功還是失敗),系統必須確保它所建模的業務處於一致的狀態,而不會是部分完成部分失敗。在現實中的數據不應該被破壞。
-
隔離性(Isolation):可能有許多事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。
-
持久性(Durability):一旦事務完成,無論發生什么系統錯誤,它的結果都不應該受到影響,這樣就能從任何系統崩潰中恢復過來。通常情況下,事務的結果被寫到持久化存儲器中。
三、事務隔離(Isolation Level)
事務隔離意味着對於某一個正在運行的事務來說,好像系統中只有這一個事務,其他並發的事務都不存在一樣。大部分情況下,很少使用完全隔離的事務。但不完全隔離的事務會帶來如下一些問題。
臟數據(Dirty Read):一個事務讀到了另一個事務的未提交的數據
不可重讀(Unrepeatable Read):一個事務讀到了另一個事務已經提交的 update 的數據導致多次查詢結果不一致
幻讀(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事務處理
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元,李四賬戶並未改動
5.1
現在需要我們來改造代碼,讓轉賬支持事務,改造如下:
在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>
測試方法不改,仍然報錯,但是張三的賬戶未被修改,說明事務生效
5.2 聲明式事務處理實現轉賬(基於AOP的 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"> <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>
測試數據無誤
5.3 聲明式事務處理實現轉賬(基於AOP的 注解 配置)
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之旅