1. 程式人生 > 實用技巧 >乾貨分享丨玩轉物聯網IoTDA服務系列五-智慧家居煤氣檢測聯動

乾貨分享丨玩轉物聯網IoTDA服務系列五-智慧家居煤氣檢測聯動

@Transactional 內部呼叫例子

在 Spring 的 AOP 代理下,只有目標方法由外部呼叫,目標方法才由 Spring 生成的代理物件來管理,這會造成自呼叫問題。
若同一類中的其他沒有@Transactional 註解的方法內部呼叫有@Transactional 註解的方法,有@Transactional 註解的方法的事務被忽略,不會發生回滾

@Service
public class A{
  
  public void action(){
    dosome();
  }
  
  @Transactional
  public void dosome(){
    doa.insert(new Object());
  }
}

如上程式碼,在方法dosome()中丟擲異常時,資料操作不會回滾

解決方案

思路: 強制使用 AspectJ 對方法進行切面

Springboot 引入 AspectJ 切面

pom.xml 中新增AspectJ:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.5</version>
</dependency>

啟動類中新增 @EnableAspectJAutoProxy(exposeProxy = true)

@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class DonngPartsApplication {

  public static void main(String[] args) {
    SpringApplication.run(DonngPartsApplication.class, args);
  }

}

注意: exposeProxy = true 若不新增,則會報:

java.lang.IllegalStateException:

Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available,

and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.

程式碼中 ((A) AopContext.currentProxy()).dosome()

修改為如下程式碼,事務就生效啦

@Service
public class A{
  
  public void action(){
    ((A) AopContext.currentProxy()).dosome();
  }
  
  @Transactional
  public void dosome(){
    doa.insert(new Object());
  }
}

@Transactional 進階

1. @Transactional 註解的屬性資訊

屬性描述
name 當在配置檔案中有多個 TransactionManager , 可以用該屬性指定選擇哪個事務管理器
propagation 事務的傳播行為,預設值為 REQUIRED
isolation 事務的隔離度,預設值採用 DEFAULT
timeout 事務的超時時間,預設值為-1。如果超過該時間限制但事務還沒有完成,則自動回滾事務
read-only 指定事務是否為只讀事務,預設值為 false;為了忽略那些不需要事務的方法,比如讀取資料,可以設定 read-only 為 true
rollback-for 用於指定能夠觸發事務回滾的異常型別,如果有多個異常型別需要指定,各型別之間可以通過逗號分隔
no-rollback- for 丟擲 no-rollback-for 指定的異常型別,不回滾事務

2. propagation 傳播行為

  • REQUIRED:如果有事務, 那麼加入事務, 沒有的話新建一個(預設情況下)
  • NOT_SUPPORTED:容器不為這個方法開啟事務
  • REQUIRES_NEW:不管是否存在事務,都建立一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務
  • MANDATORY:必須在一個已有的事務中執行,否則丟擲異常
  • NEVER:必須在一個沒有的事務中執行,否則丟擲異常(與MANDATORY相反)
  • SUPPORTS:如果其他bean呼叫這個方法,在其他bean中宣告事務,那就用事務.如果其他bean沒有宣告事務,那就不用事務.
  • NESTED: 如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則進行與PROPAGATION_REQUIRED類似的操作。

3. 事物超時設定
@Transactional(timeout=30) //預設是30秒

4. 事務隔離級別 isolation

  • READ_UNCOMMITTED:讀取未提交資料(會出現髒讀, 不可重複讀) 基本不使用
  • READ_COMMITTED:讀取已提交資料(會出現不可重複讀和幻讀)
  • REPEATABLE_READ:可重複讀(會出現幻讀)
  • SERIALIZABLE:序列化

注意

@Transactional 只能被應用到public方法上

僅僅 @Transactional 註解的出現不足於開啟事務行為,它僅僅 是一種元資料

/**
     * REQUIRED:如果存在一個事務,則支援當前事務。如果沒有事務則開啟一個新的事務。
     * REPEATABLE_READ:這種事務隔離級別可以防止髒讀,不可重複讀。但是可能出現幻像讀。它除了保證一個事務不能讀取另一個事務未提交的資料外,還保證了避免下面的情況產生(不可重複讀)
     * readOnly:不允許只讀 rollbackFor:回滾策略為Exception出現異常之後
     * TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 函式內捕獲異常時需要來設定事務回滾狀態
     * Spring Transactional一直是RD的事務神器,但是如果用不好,反會傷了自己。下面總結@Transactional經常遇到的幾個場景: 
        @Transactional 加於private方法, 無效
        @Transactional 加於未加入介面的public方法, 再通過普通介面方法呼叫, 無效
        @Transactional 加於介面方法, 無論下面呼叫的是private或public方法, 都有效
        @Transactional 加於介面方法後, 被本類普通介面方法直接呼叫, 無效
        @Transactional 加於介面方法後, 被本類普通介面方法通過介面呼叫, 有效
        @Transactional 加於介面方法後, 被它類的介面方法呼叫, 有效
        @Transactional 加於介面方法後, 被它類的私有方法呼叫後, 有效
     */