一、事務的定義及特性
(一)、事務的定義
1、事務是一個最小的不可再分割的執行單元,它由批量的DML語句構成,這些語句要麼全部執行,要不全都不執行。
2、通常一個事務對應一個完整的業務。比如典型的轉賬業務,從賬戶A中轉賬1000元給賬戶B。操作過程分爲兩步,第一步從賬戶A中扣去1000元,第二步往賬戶B中加上1000元。顯然這兩個操作要麼都得做,要麼都不做。如果只做其中一個操作,數據庫的數據就會出現問題。
3、注意:只有DML語句纔有事務,DDL和DCL沒有事務。
(二)、事務的四個特性(ACID)
1、原子性(Atomicity):原子性是指事務操作爲最小單位,不可再分。要執行就全部執行,否則都不執行,不能出現執行其中一部分的情況。
2、一致性(Consistency):一致性是指一個事務操作結束後數據庫必須仍處於一致性狀態。所謂的一致性狀態,簡單理解就是數據庫的數據總體是保持不變的。
比如轉賬過程中。事務執行前賬戶A有2000元,賬戶B有1000元。那麼“賬戶A轉賬1000元給賬戶B”這個事務執行完成後,賬戶A和賬戶B的金錢總數仍要等於3000元。
3、隔離性(Isolation):隔離性是指併發執行的事務之間互不干擾,相互隔離。
4、持久性(Durability):持久性是指事務一旦被提交,它對數據庫數據的改變就是永久性的。
(三)、事務的操作步驟
1、確保數據庫支持事務功能
A、查看數據庫支持的引擎
mysql語句:show ENGINES;
從查詢結果我們會發現數據庫支持多種引擎,但其中能提供事務功能的只有InnoDB。
B、查看待操作表的引擎
mysql語句:show create table 表名;
可以看到我的account表執行引擎已經是InnoDB了
C、如果不是InnoDB,則將其改爲InnovationDB
mysql語句:ALTER TABLE 表名 ENGINE = INNODB;
2、具體步驟
A、開啓事務
start transaction;
B、定義事務的DML語句
insert into account values(50,"test");
此時我們看下account表會發現表中仍然沒有money=50,user=test這個記錄的。說明這個DML語句還沒有真正被執行。
,
C、提交 or 回滾
提交:commit
這時我們再查看下數據庫,就會發現新紀錄已經被添加到表中了。
回滾:rollback
(需要注意的是每次執行完commit或者rollback後,整個事務就結束了,因此這裏我們需要重新開啓一個事務)
我們看下account表,會發現DML語句確實沒有被執行
二、Spring中JDBC開啓事務支持
(一)、Spring的主要配置
定義一個數據源。根據自己的實際情況來配置,筆者這裏用的是DBCP的數據源,用其他數據源也是可以的。
<!-- 定義一個使用DBCP實現的數據源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/library?characterEncoding=UTF8"
p:username="root"
p:password="admin"/>
<!-- 定義JDBC模板Bean -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
(二)、創建相應的DAO和Service類
1、AccountDAO類
package com.book.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class AccountDao {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void reduce() {
jdbcTemplate.update("update account set money=money-10 where user='紅紅'");
}
public void add(){
// int i = 2/0;
jdbcTemplate.update("update account set money=money+10 where user='小明'"); }
}
2、AccountService類
package com.book.service;
import com.book.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService {
private AccountDao accountDao;
@Autowired
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void doAccount() {
accountDao.reduce();
// int i=2/0;
accountDao.add();
}
}
3、測試代碼
package com.book.web;
import com.book.service.LendService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class Test {
public static void main(String[] args){
//進行類加載,並實例化對象
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("book-context.xml"));
//獲取實例化的對象
LendService bean = (LendService) bf.getBean("lendService");
System.out.println("Done");
try{
bean.doAccount();
}catch (Exception e){
e.printStackTrace();
}
}
}
三、Spring使用事務的兩種方式
(一)、基於AOP的註解方式
1、xml文件中添加開啓事務的註解
<!-- 第一步配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 第二步 開啓事務註解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
2、代碼中添加註解
在需要申明爲事務的方法前添加@Transactional(rollbackFor = { Exception.class })字段即可,Spring的事務回滾默認只處理RuntimeException,也就是運行時異常,對於一些程序員自己定義的異常並不會觸發事務的回滾。rollbackFor = {Exception.class}則指明我們要處理所有類型的異常。如下圖
3、運行測試代碼
運行前的數據庫狀態
A、沒有異常時運行結果如下:
B、手動構建異常(開啓事務的狀態)
運行結果如下:
用戶“紅紅”錢沒有減少,小明的錢也沒有增加。說明事務的回滾生效了。add()操作的異常導致reduce()操作被回滾,沒有執行成功。
C、手動構建異常(關閉事務的狀態)
運行結果如下,我們會發現賬號名爲“紅紅”的用戶money少了10,但是賬號名爲“小明”的用戶money卻沒有變
(二)、基於AOP的xml配置方式
1、xml中添加註解
<!-- 第一步 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 第二步 配置事務增強 -->
<tx:advice id="txadvice" transaction-manager="transactionManager">
<!-- 做事務操作 -->
<tx:attributes>
<!-- 設置進行事務操作的方法匹配規則 以do開頭的方法-->
<tx:method name="do*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 第三步 配置切面 -->
<aop:config>
<!-- 切入點 -->
<aop:pointcut expression="execution(* com.book.service.AccountService.*(..))" id="pointcut"/>
<!-- 切面 -->
<aop:advisor advice-ref="txadvice" pointcut-ref="pointcut"/>
</aop:config>
2、運行結果如下
當出現異常時兩個用戶的money都不變,說明事務回滾成功。
三、事務不起作用的常見原因
(一)、確保你所操作的表支持事務機制
最好自己先在mysql中寫個簡單的事務試試。
(二)、service層處理了異常
1、原因
service層類中使用try catch 去捕獲異常後,由於該類的異常並沒有拋出,就無法觸發事務管理機制。因爲在你將doAccount()方法申明爲事務方法後,只有當doAccount()方法向上拋出異常時纔會觸發事務的回滾機制。如果你自己在doAcount方法中用try...catch處理完異常了,事務管理器就不知道出現了異常,自然不會執行回滾操作。
2、測試
如下,我們在AccountService類的doAccount()方法中增加try...catch處理機制進行測試。
我們查看下運行結果,發現事務失效了。
3、解決方案
A、將service層的try..catch提到web層中(web開發通常分爲dao、service和web三層,不理解的直接將try...catch去掉即可)。
B、使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()方法手動回滾(不推薦,會增加代碼之間的耦合度)
運行結果如下,說明事務起作用了。