1. 程式人生 > 程式設計 >Spring事務失效問題分析及解決方案

Spring事務失效問題分析及解決方案

這篇文章主要介紹了Spring事務失效問題分析及解決方案,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

隔離級別

在 TransactionDefinition.java 介面中,定義了“四種”的隔離級別列舉:

/**
 * 【Spring 獨有】使用後端資料庫預設的隔離級別
 *
 * MySQL 預設採用的 REPEATABLE_READ隔離級別
 * Oracle 預設採用的 READ_COMMITTED隔離級別
 */
int ISOLATION_DEFAULT = -1;
 
/**
 * 最低的隔離級別,允許讀取尚未提交的資料變更,可能會導致髒讀、幻讀或不可重複讀
 */
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
 
/**
 * 允許讀取併發事務已經提交的資料,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生
 */
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
/**
 * 對同一欄位的多次讀取結果都是一致的,除非資料是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。
 */
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
/**
 * 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。
 *
 * 但是這將嚴重影響程式的效能。通常情況下也不會用到該級別。
 */
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

事務的傳播級別

事務的傳播行為,指的是當前帶有事務配置的方法,需要怎麼處理事務;例如:方法可能繼續在現有事務中執行,也可能開啟一個新事務,並在自己的事務中執行;

需要注意,事務的傳播級別,並不是資料庫事務規範中的名詞,而是 Spring 自身所定義的。通過事務的傳播級別,Spring 才知道如何處理事務,是建立一個新事務呢,還是繼續使用當前的事務;

在 TransactionDefinition.java 介面中,定義了三類七種傳播級別:

// ========== 支援當前事務的情況 ==========
 
/**
 * 如果當前存在事務,則使用該事務。
 * 如果當前沒有事務,則建立一個新的事務。
 */
int PROPAGATION_REQUIRED = 0;
/**
 * 如果當前存在事務,則使用該事務。
 * 如果當前沒有事務,則以非事務的方式繼續執行。
 */
int PROPAGATION_SUPPORTS = 1;
/**
 * 如果當前存在事務,則使用該事務。
 * 如果當前沒有事務,則丟擲異常。
 */
int PROPAGATION_MANDATORY = 2;
 
// ========== 不支援當前事務的情況 ==========
 
/**
 * 建立一個新的事務。
 * 如果當前存在事務,則把當前事務掛起。
 */
int PROPAGATION_REQUIRES_NEW = 3;
/**
 * 以非事務方式執行。
 * 如果當前存在事務,則把當前事務掛起。
 */
int PROPAGATION_NOT_SUPPORTED = 4;
/**
 * 以非事務方式執行。
 * 如果當前存在事務,則丟擲異常。
 */
int PROPAGATION_NEVER = 5;
 
// ========== 其他情況 ==========
 
/**
 * 如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行。
 * 如果當前沒有事務,則等價於 {@link TransactionDefinition#PROPAGATION_REQUIRED}
 */
int PROPAGATION_NESTED = 6;

@Transaction

@Transaction 註解是Spring的tx模組提供的,使用AOP實現的事務控制,即底層為動態代理;

  @Transaction 可以作用於介面、介面方法、類以及類方法上;當作用於類上時,該類的所有 public 方法將都具有該型別的事務屬性,也可以在方法級別使用該標註來覆蓋類級別的定義;

  事務失效

  1.異常型別錯誤,預設是RuntimException才會回滾

  2.異常被catch後沒有丟擲,需要拋異常才能回滾

  3.是否發生自身呼叫的問題

  4.註解所在位置是否為public修飾

  5.資料來源沒有配置事務管理器

  6.不支援事務的引擎,如MyIsam

  下面是一個事務失效的例子

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
  @Autowired
  private OrderMapper orderMapper;
 
  @Transactional
  @Override
  public void parent() {
    try {
      this.child();
 
    } catch (Exception e) {
      log.error("插入異常",e);
    }
 
 
    Order order = new Order();
    order.setOrderNo("parent");
    order.setStatus("0");
    order.setTitle("parent");
    order.setAmount("1000");
    orderMapper.insert(order);
  }
 
 
  @Transactional
  @Override
  public void child() {
 
    Order order = new Order();
    order.setOrderNo("child");
    order.setStatus("0");
    order.setTitle("child");
    order.setAmount("2000");
    orderMapper.insert(order);
 
    throw new RuntimeException();
  }
}

當呼叫parent方法時,會呼叫child方法,但執行完成後插入的記錄有兩條,一條是parent的,一條是child的;

這是因為上面的parent方法呼叫的child方法出現問題,@Transaction 是基於AOP的方式進行事務控制的(CglibAopProxy.java進行方法攔截,TransactionInterceptor.java進行代理物件呼叫執行方法),需要使用的是代理物件呼叫方法,上面的程式碼使用的還是this,即當前例項化物件,因此執行this.child(),方法不能被攔截增強;

將上面的parent方法修改如下

@Autowired
private ApplicationContext context;
 
private OrderService orderService;
 
@PostConstruct
public void init() {
  orderService = context.getBean(OrderService.class);
}
 
 
@Transactional
@Override
public void parent() {
  try {
    //獲取代理物件,通過代理物件呼叫child()
    orderService.child();
 
   //獲取代理物件
    //OrderService orderService = (OrderService) AopContext.currentProxy();
    //orderService.child();
  } catch (Exception e) {
    log.error("插入異常",e);
  }
  Order order = new Order();
  order.setOrderNo("parent");
  order.setStatus("0");
  order.setTitle("parent");
  order.setAmount("1000");
  orderMapper.insert(order);
}

執行parent方法,當出現異常的時候,事務會進行回滾;

如果想實現當呼叫parent方法時,呼叫child方法發生異常,只回滾child方法插入的資料,parent方法插入的資料不回滾,修改如下

@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void child() {
 
  Order order = new Order();
  order.setOrderNo("child");
  order.setStatus("0");
  order.setTitle("child");
  order.setAmount("2000");
  orderMapper.insert(order);
 
  throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)

如果當前存在事務,則掛起事務並開啟一個新事務執行,新事務執行完畢後,喚醒之前的掛起的事務,則繼續執行;如果當前不存在事務,則新建一個事務;

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。