也可以參考https://blog.csdn.net/liuhaiabc/article/details/52450167
https://blog.csdn.net/baidu_37107022/article/details/77481670
數據庫事務的隔離級別有4個,由低到高依次為Read uncommitted、Read committed、Repeatable read、Serializable,這四個級別可以逐個解決臟讀、不可重復讀、幻讀這幾類問題。
√: 可能出現 ×: 不會出現
|
臟讀 | 不可重復讀 | 幻讀 |
Read uncommitted | √ | √ | √ |
Read committed--Sql Server , Oracle | × | √ | √ |
Repeatable read--MySQL | × | × | √ |
Serializable | × | × | × |
1、事務認識
大家所了解的事務Transaction,它是一些列嚴密操作動作,要么都操作完成,要么都回滾撤銷。Spring事務管理基於底層數據庫本身的事務處理機制。數據庫事務的基礎,是掌握Spring事務管理的基礎。這篇總結下Spring事務。
事務具備ACID四種特性,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔離性)和Durability(持久性)的英文縮寫。
(1)原子性(Atomicity)
事務最基本的操作單元,要么全部成功,要么全部失敗,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
(2)一致性(Consistency)
事務的一致性指的是在一個事務執行之前和執行之后數據庫都必須處於一致性狀態。如果事務成功地完成,那么系統中所有變化將正確地應用,系統處於有效狀態。如果在事務中出現錯誤,那么系統中的所有變化將自動地回滾,系統返回到原始狀態。
(3)隔離性(Isolation)
指的是在並發環境中,當不同的事務同時操縱相同的數據時,每個事務都有各自的完整數據空間。由並發事務所做的修改必須與任何其他並發事務所做的修改隔離。事務查看數據更新時,數據所處的狀態要么是另一事務修改它之前的狀態,要么是另一事務修改它之后的狀態,事務不會查看到中間狀態的數據。
(4)持久性(Durability)
指的是只要事務成功結束,它對數據庫所做的更新就必須永久保存下來。即使發生系統崩潰,重新啟動數據庫系統后,數據庫還能恢復到事務成功結束時的狀態。
2、事務的傳播特性
事務傳播行為就是多個事務方法調用時,如何定義方法間事務的傳播。Spring定義了7中傳播行為:
(1)propagation_requierd:如果當前沒有事務,就新建一個事務,如果已存在一個事務中,加入到這個事務中,這是Spring默認的選擇。
(2)propagation_supports:支持當前事務,如果沒有當前事務,就以非事務方法執行。
(3)propagation_mandatory:使用當前事務,如果沒有當前事務,就拋出異常。
(4)propagation_required_new:新建事務,如果當前存在事務,把當前事務掛起。
(5)propagation_not_supported:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
(6)propagation_never:以非事務方式執行操作,如果當前事務存在則拋出異常。
(7)propagation_nested:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與propagation_required類似的操作。
3、事務的隔離級別
(1)read uncommited:是最低的事務隔離級別,它允許另外一個事務可以看到這個事務未提交的數據。
(2)read commited:保證一個事物提交后才能被另外一個事務讀取。另外一個事務不能讀取該事物未提交的數據。
(3)repeatable read:這種事務隔離級別可以防止臟讀,不可重復讀。但是可能會出現幻象讀。它除了保證一個事務不能被另外一個事務讀取未提交的數據之外還避免了以下情況產生(不可重復讀)。
(4)serializable:這是花費最高代價但最可靠的事務隔離級別。事務被處理為順序執行。除了防止臟讀,不可重復讀之外,還避免了幻象讀
(5)臟讀、不可重復讀、幻象讀概念說明:
a.臟讀:指當一個事務正字訪問數據,並且對數據進行了修改,而這種數據還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。因為這個數據還沒有提交那么另外一個事務讀取到的這個數據我們稱之為臟數據。依據臟數據所做的操作肯能是不正確的。
b.不可重復讀:指在一個事務內,多次讀同一數據。在這個事務還沒有執行結束,另外一個事務也訪問該同一數據,那么在第一個事務中的兩次讀取數據之間,由於第二個事務的修改第一個事務兩次讀到的數據可能是不一樣的,這樣就發生了在一個事物內兩次連續讀到的數據是不一樣的,這種情況被稱為是不可重復讀。
c.幻象讀:一個事務先后讀取一個范圍的記錄,但兩次讀取的紀錄數不同,我們稱之為幻象讀(兩次執行同一條 select 語句會出現不同的結果,第二次讀會增加一數據行,並沒有說這兩次執行是在同一個事務中)
4、事務幾種實現方式
(1)編程式事務管理對基於 POJO 的應用來說是唯一選擇。我們需要在代碼中調用beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是編程式事務管理。
(2)基於 TransactionProxyFactoryBean的聲明式事務管理
(3)基於 @Transactional 的聲明式事務管理
(4)基於Aspectj AOP配置事務
5、舉例說明事務不同實現
編程式事務基本已經OUT了,在上上家公司的老工程還能找到,所有就省略了,主要回顧 下聲明式事務。
以用戶購買股票為例
新建用戶對象、股票對象、以及dao、service層
/**
* 賬戶對象
*
*/
public class Account {
private int accountid;
private String name;
private String balance;
public int getAccountid() {
return accountid;
}
public void setAccountid(int accountid) {
this.accountid = accountid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBalance() {
return balance;
}
public void setBalance(String balance) {
this.balance = balance;
}
}
/**
* 股票對象
*
*/
public class Stock {
private int stockid;
private String name;
private Integer count;
public Stock() {
super();
}
public Stock(int stockid, String name, Integer count) {
super();
this.stockid = stockid;
this.name = name;
this.count = count;
}
public int getStockid() {
return stockid;
}
public void setStockid(int stockid) {
this.stockid = stockid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}
DAO層
public interface AccountDao {
void addAccount(String name,double money);
void updateAccount(String name,double money,boolean isbuy);
}
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void addAccount(String name, double money) {
String sql = "insert account(name,balance) values(?,?);";
this.getJdbcTemplate().update(sql,name,money);
}
@Override
public void updateAccount(String name, double money, boolean isbuy) {
String sql = "update account set balance=balance+? where name=?";
if(isbuy)
sql = "update account set balance=balance-? where name=?";
this.getJdbcTemplate().update(sql, money,name);
}
}
public interface StockDao {
void addStock(String sname,int count);
void updateStock(String sname,int count,boolean isbuy);
}
public class StockDaoImpl extends JdbcDaoSupport implements StockDao {
@Override
public void addStock(String sname, int count) {
String sql = "insert into stock(name,count) values(?,?)";
this.getJdbcTemplate().update(sql,sname,count);
}
@Override
public void updateStock(String sname, int count, boolean isbuy) {
String sql = "update stock set count = count-? where name = ?";
if(isbuy)
sql = "update stock set count = count+? where name = ?";
this.getJdbcTemplate().update(sql, count,sname);
}
}
Service
public interface BuyStockService {
public void addAccount(String accountname, double money);
public void addStock(String stockname, int amount);
public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException;
}
public class BuyStockServiceImpl implements BuyStockService{
private AccountDao accountDao;
private StockDao stockDao;
@Override
public void addAccount(String accountname, double money) {
accountDao.addAccount(accountname,money);
}
@Override
public void addStock(String stockname, int amount) {
stockDao.addStock(stockname,amount);
}
@Override
public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException {
boolean isBuy = true;
accountDao.updateAccount(accountname, money, isBuy);
if(isBuy==true){
throw new BuyStockException("購買股票發生異常");
}
stockDao.updateStock(stockname, amount, isBuy);
}
public AccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public StockDao getStockDao() {
return stockDao;
}
public void setStockDao(StockDao stockDao) {
this.stockDao = stockDao;
}
}
自定義異常類
public class BuyStockException extends Exception {
public BuyStockException() {
super();
}
public BuyStockException(String message) {
super(message);
}
}
(1)基於 TransactionProxyFactoryBean的聲明式事務管理
<?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:mvc="http://www.springframework.org/schema/mvc"
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-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-aop-4.2.xsd
">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 注冊數據源 C3P0 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="accountDao" class="transaction.test2.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="stockDao" class="transaction.test2.dao.StockDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="buyStockService" class="transaction.test2.service.BuyStockServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="stockDao" ref="stockDao"></property>
</bean>
<!-- 事務管理器 -->
<bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事務代理工廠 -->
<!-- 生成事務代理對象 -->
<bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="myTracnsactionManager"></property>
<property name="target" ref="buyStockService"></property>
<property name="transactionAttributes">
<props>
<!-- 主要 key 是方法
ISOLATION_DEFAULT 事務的隔離級別
PROPAGATION_REQUIRED 傳播行為
-->
<prop key="add*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
<!-- -Exception 表示發生指定異常回滾,+Exception 表示發生指定異常提交 -->
<prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-BuyStockException</prop>
</props>
</property>
</bean>
</beans>
測試入口
public static void main(String[] args) {
String resouce = "transaction/test2/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(resouce);
BuyStockService buyStockService = (BuyStockService) applicationContext.getBean("serviceProxy");
// buyStockService.openAccount("小鄭", 5000);
// buyStockService.openStock("知曉科技", 0);
try {
buyStockService.buyStock("小鄭", 1000, "知曉科技", 100);
} catch (BuyStockException e) {
e.printStackTrace();
}
}
發生異常賬戶金額不能減,股票不能增加
(2)基於 @Transactional 的聲明式事務管理
其他類不做改變,只改變購買股票接口實現類和配置文件
public class BuyStockServiceImpl implements BuyStockService{
private AccountDao accountDao;
private StockDao stockDao;
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
@Override
public void addAccount(String accountname, double money) {
accountDao.addAccount(accountname,money);
}
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
@Override
public void addStock(String stockname, int amount) {
stockDao.addStock(stockname,amount);
}
public BuyStockServiceImpl() {
// TODO Auto-generated constructor stub
}
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=BuyStockException.class)
@Override
public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException {
boolean isBuy = true;
accountDao.updateAccount(accountname, money, isBuy);
if(isBuy==true){
throw new BuyStockException("購買股票發生異常");
}
stockDao.updateStock(stockname, amount, isBuy);
}
public AccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public StockDao getStockDao() {
return stockDao;
}
public void setStockDao(StockDao stockDao) {
this.stockDao = stockDao;
}
}
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 注冊數據源 C3P0 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="accountDao" class="transaction.test3.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="stockDao" class="transaction.test3.dao.StockDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="buyStockService" class="transaction.test3.service.BuyStockServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="stockDao" ref="stockDao"></property>
</bean>
<!-- 事務管理器 -->
<bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 啟用事務注解 -->
<tx:annotation-driven transaction-manager="myTracnsactionManager"/>
可以看出,使用@Transactional注解的方式配置文件要簡單的多,將事務交給事務注解驅動。它有個缺陷是他會把所有的連接點都作為切點將事務織入進去,顯然只需要在buyStock()方法織入事務即可。下面看看最后一種實現,它就可以精准的織入到指定的連接點
(3)基於Aspectj AOP配置事務
public class BuyStockServiceImpl implements BuyStockService{
private AccountDao accountDao;
private StockDao stockDao;
@Override
public void addAccount(String accountname, double money) {
accountDao.addAccount(accountname,money);
}
@Override
public void addStock(String stockname, int amount) {
stockDao.addStock(stockname,amount);
}
public BuyStockServiceImpl() {
// TODO Auto-generated constructor stub
}
@Override
public void buyStock(String accountname, double money, String stockname, int amount) throws BuyStockException {
boolean isBuy = true;
accountDao.updateAccount(accountname, money, isBuy);
if(isBuy==true){
throw new BuyStockException("購買股票發生異常");
}
stockDao.updateStock(stockname, amount, isBuy);
}
public AccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public StockDao getStockDao() {
return stockDao;
}
public void setStockDao(StockDao stockDao) {
this.stockDao = stockDao;
}
}
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 注冊數據源 C3P0 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="accountDao" class="transaction.test4.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="stockDao" class="transaction.test4.dao.StockDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="buyStockService" class="transaction.test4.service.BuyStockServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="stockDao" ref="stockDao"></property>
</bean>
<!-- 事務管理器 -->
<bean id="myTracnsactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="myTracnsactionManager">
<tx:attributes>
<!-- 為連接點指定事務屬性 -->
<tx:method name="add*" isolation="DEFAULT" propagation="REQUIRED"/>
<tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="BuyStockException"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!-- 切入點配置 -->
<aop:pointcut expression="execution(* *..service.*.*(..))" id="point"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="point"/>
</aop:config>
執行接口與其他方式一樣。
---------------------
作者:WilliamDream
來源:CSDN
原文:https://blog.csdn.net/chinacr07/article/details/78817449
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!