1 問題背景
有時候,我們總是需要再SpringBoot2中對一個Service方法做一個完整的事務,發現異常時,進行回滾,然后又能返回錯誤信息。
事務定義
事務,就是一組操作數據庫的動作集合。事務是現代數據庫理論中的核心概念之一。如果一組處理步驟或者全部發生或者一步也不執行,我們稱該組處理步驟為一個事務。當所有的步驟像一個操作一樣被完整地執行,我們稱該事務被提交。由於其中的一部分或多步執行失敗,導致沒有步驟被提交,則事務必須回滾到最初的系統狀態。
事務特點
1.原子性:一個事務中所有對數據庫的操作是一個不可分割的操作序列,要么全做要么全不做
2.一致性:數據不會因為事務的執行而遭到破壞
3.隔離性:一個事物的執行,不受其他事務的干擾,即並發執行的事物之間互不干擾
4.持久性:一個事物一旦提交,它對數據庫的改變就是永久的。
2 @Transactional 事務實現機制
Spring 為事務管理提供了豐富的功能支持。Spring 事務管理分為編碼式和聲明式的兩種方式。
(1)編程式事務指的是通過編碼方式實現事務;
(2)聲明式事務基於 AOP,將具體業務邏輯與事務處理解耦。
聲明式事務管理使業務代碼邏輯不受污染, 因此在實際使用中聲明式事務用的比較多。
聲明式事務有兩種方式:
(1)一種是在配置文件(xml)中做相關的事務規則聲明,
(2)另一種是基於@Transactional 注解的方式。
注釋配置是目前流行的使用方式,因此本文將着重介紹基於@Transactional 注解的事務管理。
在應用系統調用聲明了 @Transactional 的目標方法時,Spring Framework 默認使用 AOP 代理,在代碼運行時生成一個代理對象,根據 @Transactional 的屬性配置信息,這個代理對象決定該聲明 @Transactional 的目標方法是否由攔截器 TransactionInterceptor 來使用攔截,在 TransactionInterceptor 攔截時,會在目標方法開始執行之前創建並加入事務,並執行目標方法的邏輯, 最后根據執行情況是否出現異常,利用抽象事務管理器 AbstractPlatformTransactionManager 操作數據源 DataSource 提交或回滾事務。
Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 兩種,以 CglibAopProxy 為例,對於 CglibAopProxy,需要調用其內部類的 DynamicAdvisedInterceptor 的 intercept 方法。對於 JdkDynamicAopProxy,需要調用其 invoke 方法。
開啟事務
方式1:在 xml 配置中的事務配置信息
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
方式2:使用@EnableTransactionManagement 注解也可以啟用事務管理功能
3 場景
3.1 場景一:自動回滾(直接拋出,不try/catch)
@Override
@Transactional(rollbackFor = Exception.class)
public Object submitOrder() throws Exception {
success();
//假如exception這個操作數據庫的方法會拋出異常,方法success()對數據庫的操作會回滾。
exception();
return ApiReturnUtil.success();
}
3.2 場景二:手動回滾(進行try/catch,回滾並拋出)
@Override
@Transactional(rollbackFor = Exception.class)
public Object submitOrder (){
success();
try {
exception();
} catch (Exception e) {
e.printStackTrace();
//手工回滾異常
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return ApiReturnUtil.error();
}
return ApiReturnUtil.success();
}
3.3 補充:回滾部分異常
使用Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 設置回滾點。
使用TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);回滾到savePoint。
@Override
@Transactional(rollbackFor = Exception.class)
public Object submitOrder (){
success();
//只回滾以下異常,
Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
try {
exception();
} catch (Exception e) {
e.printStackTrace();
//手工回滾異常
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
return ApiReturnUtil.error();
}
return ApiReturnUtil.success();
}
3.4 使用DataSourceTransactionManager
springboot 開啟事務以及手動提交事務,可以在服務類上加上兩個注解
-
@Autowired
-
DataSourceTransactionManager dataSourceTransactionManager;
-
@Autowired
-
TransactionDefinition transactionDefinition;
手動開啟事務
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
手動提交事務
dataSourceTransactionManager.commit(transactionStatus);//提交
手動回滾事務
dataSourceTransactionManager.rollback(transactionStatus);//最好是放在catch 里面,防止程序異常而事務一直卡在哪里未提交
4 spring boot controller設置 @Transactional 不回滾的解決辦法
在spring boot 中,使用事務非常簡單,直接在方法上面加入@Transactional 就可以實現,以下是我的做法
@GetMapping("delete")
@ResponseBody
@Transactional
public void delete(@RequestParam("id") int id) {
try {
//delete country
this.repository.delete(id);
if(id == 1){
throw Exception("測試事務");
}
//delete city
this.repository.deleteByCountryId(id);
}catch (Exception e){
logger.error("delete false:" + e.getMessage());
return new MessageBean(101,"delete false");
}
}
發現事務不回滾,即 this.repository.delete(id); 成功把數據刪除了。
原因:
默認spring事務只在發生未被捕獲的 RuntimeException 時才回滾。
spring aop 異常捕獲原理:被攔截的方法需顯式拋出異常,並不能經任何處理,這樣aop代理才能捕獲到方法的異常,才能進行回滾,默認情況下aop只捕獲 RuntimeException 的異常,但可以通過配置來捕獲特定的異常並回滾。
換句話說在service的方法中不使用try catch 或者在catch中最后加上throw new runtimeexcetpion(),這樣程序異常時才能被aop捕獲進而回滾。
解決方案:
方案1:例如service層處理事務,那么service中的方法中不做異常捕獲,或者在catch語句中最后增加throw new RuntimeException()語句,以便讓aop捕獲異常再去回滾,並且在service上層(webservice客戶端,view層action)要繼續捕獲這個異常並處理
方案2:在service層方法的catch語句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();語句,手動回滾,這樣上層就無需去處理異常
@GetMapping("delete")
@ResponseBody
@Transactional
public Object delete(@RequestParam("id") int id){
if (id < 1){
return new MessageBean(101,"parameter wrong: id = " + id) ;
}
try {
//delete country
this.countryRepository.delete(id);
//delete city
this.cityRepository.deleteByCountryId(id);
return new MessageBean(200,"delete success");
}catch (Exception e){
logger.error("delete false:" + e.getMessage());
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return new MessageBean(101,"delete false");
}
}
5 避免 Spring 的 AOP 的自調用問題
在 Spring 的 AOP 代理下,只有目標方法由外部調用,目標方法才由 Spring 生成的代理對象來管理,這會造成自調用問題。若同一類中的其他沒有@Transactional 注解的方法內部調用有@Transactional 注解的方法,有@Transactional 注解的方法的事務被忽略,不會發生回滾。見清單 5 舉例代碼展示。
清單 5.自調用問題舉例
@Service
public class OrderService {
private void insert() {
insertOrder();
}
@Transactional
public void insertOrder() {
//insert log info
//insertOrder
//updateAccount
}
}
insertOrder 盡管有@Transactional 注解,但它被內部方法 insert()調用,事務被忽略,出現異常事務不會發生回滾。
這樣一個錯org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope
比如例子:
下面我們來看兩種寫法,第一種
-
@Transactional
-
public UserEntity login1(UserEntity user) {
-
userDao.update(6);
-
if(userDao.update(6)){
-
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
-
}
-
return user;
-
}
第二種
-
public UserEntity login(UserEntity user) {
-
this.test();
-
return user;
-
}
-
-
@Transactional
-
public void test(){
-
userDao.update(6);
-
if(userDao.update(6)){
-
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
-
}
-
}
第一種寫法的時候,回滾是起作用的.
第二種寫法的時候就會報錯,錯誤就是一開始提到的: org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope
為什么會這樣呢?
此錯在沒有Transaction無法回滾事務。自調用導致@Transactional 失效。
spring里事務是用注解配置的,當一個方法沒有接口,單單只是一個方法不是服務時,事務的注解是不起作用的,需要回滾時就會報錯。
出現這個問題的根本原因在於AOP的實現原理。由於@Transactional 的實現原理是AOP,AOP的實現原理是動態代理,換句話說,自調用時不存在代理對象的調用,這時不會產生我們注解@Transactional 配置的參數,自然無效了。
雖然可以直接從容器中獲取代理對象,但這樣有侵入之嫌,不推薦。
也是在此記錄一筆:
事務必須用在服務上,且一個服務一個事務,不得嵌套。
sping的事務是通過注解配置上去的,而下面的那個方法並沒有接口,在實現類里面只是一個簡單的方法而已,對於事務的注解來說沒有任何作用,所以在這個方法里面調用回滾的方法自然就報錯了。
所以在以后的項目中如果你要使用事務,那么請記住,一個服務一個事務,一次請求一個事務,千萬不要想着用調用方法,然后再一個方法上面加事務。你只能調用另外一個服務,在另外一個服務上面加事務。
解決方法:
1、可以在類服務上加事務:
-
@Transactional
-
public class MyTransactional {
-
public UserEntity login(UserEntity user) {
-
this.test();
-
return user;
-
}
-
-
//@Transactional 服務上加了@Transactional后 這里可以不加
-
public void test(){
-
userDao.update(6);
-
if(userDao.update(6)){
-
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
-
}
-
}
-
}
2、使用AspectJ 取代 Spring AOP 代理
上面的兩個問題@Transactional 注解只應用到 public 方法和自調用問題,是由於使用 Spring AOP 代理造成的。為解決這兩個問題,使用 AspectJ 取代 Spring AOP 代理。
需要將下面的 AspectJ 信息添加到 xml 配置信息中。
清單 6. AspectJ 的 xml 配置信息
<tx:annotation-driven mode="aspectj" />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</bean
class="org.springframework.transaction.aspectj.AnnotationTransactionAspect"
factory-method="aspectOf">
<property name="transactionManager" ref="transactionManager" />
</bean>
同時在 Maven 的 pom 文件中加入 spring-aspects 和 aspectjrt 的 dependency 以及 aspectj-maven-plugin。
清單 7. AspectJ 的 pom 配置信息
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.9</version>
<configuration>
<showWeaveInfo>true</showWeaveInfo>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
5 詳細說明
5.1 事務管理方式
在Spring中,事務有兩種實現方式,分別是編程式事務管理和聲明式事務管理兩種方式。
- 編程式事務管理: 編程式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於編程式事務管理,spring推薦使用TransactionTemplate。
- 聲明式事務管理: 建立在AOP之上的。其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況提交或者回滾事務。
- 聲明式事務管理不需要入侵代碼,通過@Transactional就可以進行事務操作,更快捷而且簡單,推薦使用。
5.2 事務處理 @Transactional注解的使用
- Spring團隊的建議是你在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現的任何接口上。你當然可以在接口上使用 @Transactional 注解,但是這將只能當你設置了基於接口的代理時它才生效。因為注解是不能繼承的,這就意味着如果你正在使用基於類的代理時,那么事務的設置將不能被基於類的代理所識別,而且對象也將不會被事務代理所包裝(將被確認為嚴重的)。因此,請接受Spring團隊的建議並且在具體的類上使用 @Transactional 注解。
- @Transactional 注解應該只被應用到 public 修飾的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不會報錯, 但是這個被注解的方法將不會展示已配置的事務設置。
- Spring的AOP即聲明式事務管理默認是針對unchecked exception回滾。也就是默認對RuntimeException()異常或是其子類進行事務回滾。checked異常,即Exception可try{}捕獲的不會回滾,因此對於我們自定義異常,通過rollbackFor進行設定。
- 如果我們需要捕獲異常后,同時進行回滾,通過TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();進行手動回滾操作。
- 使用Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 設置回滾點,使用TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);回滾到savePoint。
5.3 事務提交方式
默認情況下,數據庫處於自動提交模式。每一條語句處於一個單獨的事務中,在這條語句執行完畢時,如果執行成功則隱式的提交事務,如果執行失敗則隱式的回滾事務。
對於正常的事務管理,是一組相關的操作處於一個事務之中,因此必須關閉數據庫的自動提交模式。不過,這個我們不用擔心,spring會將底層連接的自動提交特性設置為false。也就是在使用spring進行事物管理的時候,spring會將是否自動提交設置為false,等價於JDBC中的 connection.setAutoCommit(false);,在執行完之后在進行提交,connection.commit(); 。
5.4 注解@Transactional常用配置
參 數 名 稱 | 功 能 描 述 |
---|---|
readOnly | 該屬性用於設置當前事務是否為只讀事務,設置為true表示只讀,false則表示可讀寫,默認值為false。例如:@Transactional(readOnly=true) |
rollbackFor | rollbackFor 該屬性用於設置需要進行回滾的異常類數組,當方法中拋出指定異常數組中的異常時,則進行事務回滾。例如:指定單一異常類:@Transactional(rollbackFor=RuntimeException.class)指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
rollbackForClassName | 該屬性用於設置需要進行回滾的異常類名稱數組,當方法中拋出指定異常名稱數組中的異常時,則進行事務回滾。例如:指定單一異常類名稱@Transactional(rollbackForClassName=”RuntimeException”)指定多個異常類名稱:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”}) |
noRollbackFor | 該屬性用於設置不需要進行回滾的異常類數組,當方法中拋出指定異常數組中的異常時,不進行事務回滾。例如:指定單一異常類:@Transactional(noRollbackFor=RuntimeException.class)指定多個異常類:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
noRollbackForClassName | 該屬性用於設置不需要進行回滾的異常類名稱數組,當方法中拋出指定異常名稱數組中的異常時,不進行事務回滾。例如:指定單一異常類名稱:@Transactional(noRollbackForClassName=”RuntimeException”)指定多個異常類名稱:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”}) |
propagation | 該屬性用於設置事務的傳播行為。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation | 該屬性用於設置底層數據庫的事務隔離級別,事務隔離級別用於處理多事務並發的情況,通常使用數據庫的默認隔離級別即可,基本不需要進行設置 |
timeout | 該屬性用於設置事務的超時秒數,默認值為-1表示永不超時 事物超時設置: @Transactional(timeout=30) //默認是30秒 |
5.5 Propagation的屬性(事務的傳播行為)
例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
Propagation屬性 |
含義 |
REQUIRED |
默認值 在有transaction狀態下執行;如當前沒有transaction,則創建新的transaction; |
SUPPORTS |
如當前有transaction,則在transaction狀態下執行;如果當前沒有transaction,在無transaction狀態下執行; |
MANDATORY |
必須在有transaction狀態下執行,如果當前沒有transaction,則拋出異常IllegalTransactionStateException; |
REQUIRES_NEW |
創建新的transaction並執行;如果當前已有transaction,則將當前transaction掛起; |
NOT_SUPPORTED |
在無transaction狀態下執行;如果當前已有transaction,則將當前transaction掛起; |
NEVER |
在無transaction狀態下執行;如果當前已有transaction,則拋出異常IllegalTransactionStateException。 |
5.6 事務5種隔離級別
例如:@Transactional(isolation = Isolation.READ_COMMITTED)
隔離級別 | 含義 |
---|---|
DEFAULT | 這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別. 另外四個與JDBC的隔離級別相對應; |
READ_UNCOMMITTED | 最低的隔離級別。事實上我們不應該稱其為隔離級別,因為在事務完成前,其他事務可以看到該事務所修改的數據。而在其他事務提交前,該事務也可以看到其他事務所做的修改。可能導致臟,幻,不可重復讀 |
READ_COMMITTED | 大多數數據庫的默認級別。在事務完成前,其他事務無法看到該事務所修改的數據。遺憾的是,在該事務提交后,你就可以查看其他事務插入或更新的數據。這意味着在事務的不同點上,如果其他事務修改了數據,你就會看到不同的數據。可防止臟讀,但幻讀和不可重復讀仍可以發生。 |
REPEATABLE_READ | 比ISOLATION_READ_COMMITTED更嚴格,該隔離級別確保如果在事務中查詢了某個數據集,你至少還能再次查詢到相同的數據集,即使其他事務修改了所查詢的數據。然而如果其他事務插入了新數據,你就可以查詢到該新插入的數據。可防止臟讀,不可重復讀,但幻讀仍可能發生。 |
SERIALIZABLE | 完全服從ACID的隔離級別,確保不發生臟讀、不可重復讀和幻影讀。這在所有隔離級別中也是最慢的,因為它通常是通過完全鎖定當前事務所涉及的數據表來完成的。代價最大、可靠性最高的隔離級別,所有的事務都是按順序一個接一個地執行。避免所有不安全讀取。 |
5.7 事務回滾規則
指示spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內拋出異常。spring事務管理器會捕捉任何未處理的異常,然后依據規則決定是否回滾拋出異常的事務。
默認配置下,spring只有在拋出的異常為運行時unchecked異常時才回滾該事務,也就是拋出的異常為RuntimeException的子類(Errors也會導致事務回滾),而拋出checked異常則不會導致事務回滾。
可以明確的配置在拋出那些異常時回滾事務,包括checked異常。也可以明確定義那些異常拋出時不回滾事務。
5.8 事物注意事項
- 要根據實際的需求來決定是否要使用事物,最好是在編碼之前就考慮好,不然到以后就難以維護;
- 如果使用了事物,請務必進行事物測試,因為很多情況下以為事物是生效的,但是實際上可能未生效!
- 事物@Transactional的使用要放再類的公共(public)方法中,需要注意的是在 protected、private 方法上使用 @Transactional 注解,它也不會報錯(IDEA會有提示),但事務無效。
- 事物@Transactional是不會對該方法里面的子方法生效!也就是你在公共方法A聲明的事物@Transactional,但是在A方法中有個子方法B和C,其中方法B進行了數據操作,但是該異常被B自己處理了,這樣的話事物是不會生效的!反之B方法聲明的事物@Transactional,但是公共方法A卻未聲明事物的話,也是不會生效的!如果想事物生效,需要將子方法的事務控制交給調用的方法,在子方法中使用rollbackFor注解指定需要回滾的異常或者將異常拋出交給調用的方法處理。一句話就是在使用事物的時候子方法最好將異常拋出!
- 事物@Transactional由spring控制的時候,它會在拋出異常的時候進行回滾。如果自己使用catch捕獲了處理了,是不生效的,如果想生效可以進行手動回滾或者在catch里面將異常拋出,比如throw new RuntimeException();。
-
@Transactional可以放在Controller下面直接起作用,看到網上好多同學說要放到@Component下面或者@Service下面,經過試驗,可以不用放在這兩個下面也起作用。
-
@Transactional引入包問題,她有兩個包:import javax.transaction.Transactional; 和 import org.springframework.transaction.annotation.Transactional; 這兩個都可以用,對比了一下他們兩個的方法和屬性,發現后面的比前面的強大。建議后后面的。
-
PlatformTransactionManager 這個接口中定義了三個方法 getTransaction創建事務,commit 提交事務,rollback 回滾事務。她的實現類是 AbstractPlatformTransactionManager這個。
5.9 事務並發會產生什么問題
術語 | 含義 |
---|---|
臟讀 | A事務讀取到了B事務還未提交的數據,如果B未提交的事務回滾了,那么A事務讀取的數據就是無效的,這就是數據臟讀 |
不可重復讀 | 在同一個事務中,多次讀取同一數據返回的結果不一致,這是由於讀取事務在進行操作的過程中,如果出現更新事務,它必須等待更新事務執行成功提交完成后才能繼續讀取數據,這就導致讀取事務在前后讀取的數據不一致的狀況出現 |
幻讀 | A事務讀取了幾行記錄后,B事務插入了新數據,並且提交了插入操作,在后續操作中A事務就會多出幾行原本不存在的數據,就像A事務出現幻覺,這就是幻讀 |
1)第一類丟失更新:
在沒有事務隔離的情況下,兩個事務都同時更新一行數據,但是第二個事務卻中途失敗退出, 導致對數據的兩個修改都失效了。
例如:
張三的工資為5000,事務A中獲取工資為5000,事務B獲取工資為5000,匯入100,並提交數據庫,工資變為5100,
隨后
事務A發生異常,回滾了,恢復張三的工資為5000,這樣就導致事務B的更新丟失了。
2)臟讀:
臟讀就是指當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。
例如:
張三的工資為5000,事務A中把他的工資改為8000,但事務A尚未提交。
與此同時,
事務B正在讀取張三的工資,讀取到張三的工資為8000。
隨后,
事務A發生異常,而回滾了事務。張三的工資又回滾為5000。
最后,
事務B讀取到的張三工資為8000的數據即為臟數據,事務B做了一次臟讀。
3)不可重復讀:
是指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那么,在第一個事務中的兩次讀數據之間,由於第二個事務的修改,那么第一個事務兩次讀到的的數據可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為是不可重復讀。
例如:
在事務A中,讀取到張三的工資為5000,操作沒有完成,事務還沒提交。
與此同時,
事務B把張三的工資改為8000,並提交了事務。
隨后,
在事務A中,再次讀取張三的工資,此時工資變為8000。在一個事務中前后兩次讀取的結果並不致,導致了不可重復讀。
4)第二類丟失更新:
不可重復讀的特例。
有兩個並發事務同時讀取同一行數據,然后其中一個對它進行修改提交,而另一個也進行了修改提交。這就會造成第一次寫操作失效。
例如:
在事務A中,讀取到張三的存款為5000,操作沒有完成,事務還沒提交。
與此同時,
事務B,存儲1000,把張三的存款改為6000,並提交了事務。
隨后,
在事務A中,存儲500,把張三的存款改為5500,並提交了事務,這樣事務A的更新覆蓋了事務B的更新。
5)幻讀:
是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那么,以后就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。
例如:
目前工資為5000的員工有10人,事務A讀取所有工資為5000的人數為10人。
此時,
事務B插入一條工資也為5000的記錄。
這是,事務A再次讀取工資為5000的員工,記錄為11人。此時產生了幻讀。
提醒:
不可重復讀的重點是修改,同樣的條件,你讀取過的數據,再次讀取出來發現值不一樣了
幻讀的重點在於新增或者刪除,同樣的條件,第 1 次和第 2 次讀出來的記錄數不一樣
6 結語
由於本人才接觸SprintBoot,文中如有沒說清楚的地方,希望大家能在評論區指出,以幫助我將博文寫得更好。
參考;
https://www.solves.com.cn/it/cxkf/yy/JAVA/2019-08-30/4060.html
https://blog.csdn.net/moshowgame/article/details/84561654
https://zhuanlan.zhihu.com/p/78699158
https://juejin.im/post/5b69949bf265da0f4f1663af
https://blog.csdn.net/nextyu/article/details/78669997
https://blog.csdn.net/han1196639488/article/details/77442946
https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html
備注:本人轉自https://blog.csdn.net/zzhongcy/article/details/102893309 的文章,供大家深刻學習