中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring事務相關問題解決方案

發布時間:2020-10-05 11:19:04 來源:腳本之家 閱讀:140 作者:min.jiang 欄目:編程語言

有些spring相關的知識點之前一直沒有仔細研究:比如spring的事務,并不是沒有使用,也曾經簡單的在某些需要事務處理的方法上通過增加事務注解來實現事務功能,僅僅是跟隨使用(甚至并未測試過事務的正確性),至于如何在項目中配置事務,如何才能將事務寫正確,事務的其它的一些原理性的東西從未花時間研究。最近同事正好拋出了一個問題,借此機會學習了一遍。

問題一:增加了readOnly=true的事務中包含寫操作,為什么線上運行這段代碼是正常的呢?

@Transactional(readOnly = true)
  public Integer getUID(String key, String type) {
    keyGeneratorDao.insert(key, type);
    keyGeneratorDao.update(key, type);
    return keyGeneratorDao.select(key, type);
  }

我為什么對這個問題感興趣?

不懂這個readOnly參數的含義,之前寫@Transactional的注解,那都是使用的默認值,不帶顯示參數。提出配置了readOnly參數后,理論上應該程序報錯而實際上沒有報錯,想搞清楚為什么。

開始寫單元測試:

在單元測試類中寫事務函數以及測試方法

@Autowired
  private IkeyGeneratorDao keyGeneratorDao;

  @Transactional(readOnly=true)
  public Integer getId(String key, String type){
    return keyGeneratorDao.select(key, type);
  }
  @Transactional
  public Integer getUID(String key, String type) {
    keyGeneratorDao.insert(key, type);
    keyGeneratorDao.update(key, type);
    return this.getId(key,type);
  }
  @Test
  public void testCreateGuid(){
    int guid=this.getUID("12345", "jim");
    System.out.println(guid);
  }

測試結果顯示正常,與上面提到的不允許進行寫操作的觀點相反,于是想起典型的事務生效問題。

挖的第一個坑:如果事務采用的是cglib動態代理,調用的方法與事務方法處在同一個類中事務不生效。

將兩個事務事務轉移到單獨的類中,然后測試,類代碼省略,只是將上面兩個標記了@Transactional的方法封裝在一個單獨的類中。

@Autowired
  private KeyGeneratorService keyService;

  @Test
  public void testCreateGuid2(){
    int guid=this.keyService.getUID("12345", "jim");
    System.out.println(guid);
  }

測試結果顯示也是正常,于是想確認下事務到底是否生效,加入異常以測試數據是否回滾,修改代碼如下:

@Transactional
  public Integer getUID3(String key, String type) {
    keyGeneratorDao.insert(key, type);
    Integer.parseInt("aaa");//throw exception
    keyGeneratorDao.update(key, type);
}

測試結果顯示事務回滾正常,可以排除事務環境配置問題。

挖的第二個坑:做測試一定要與原問題代碼盡量保持一致,否則會產生其它的不明原因影響判斷。通過對比原問題的代碼發現我寫的測試代碼與問題代碼有區別,readOnly是加在包含有寫操作的方法上,而我的是兩個方法,只有在讀的方法上增加了readOnly,于時再次修改代碼:

@Transactional(readOnly = true)
  public Integer getUID4(String key, String type) {
    keyGeneratorDao.insert(key, type);
    keyGeneratorDao.update(key, type);
    return keyGeneratorDao.select(key, type);
  }

測試結果顯示運行不正常,提示如下錯誤:Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed,到這的確說明在在加了readOnly=true的事務內是不允許寫入操作的。為什么這段代碼在線上運行是成功的呢,于時查看前端的調用,發現前端調用的并不是直接標識了Transactional的方法,而是根據不同的具體業務重新包裝的方法,比如我們需要生成訂單的編號,前端只調用genOrderCode而不調用getUID。

非事務方法在內部調用了本類事務方法,然后非事務方法被外部調用ServicegenOrderCode,是一個非事務方法,內部調用了getUIDgetUID,是一個事務方法

@Autowired
  private IkeyGeneratorDao keyGeneratorDao;

  @Transactional(readOnly = true)
  public Integer getUID(String key, String type) {
    keyGeneratorDao.insert(key, type);
    keyGeneratorDao.update(key, type);
    return keyGeneratorDao.select(key, type);
  }

  public String genOrderCode(Date orderDate)
  {
    SimpleDateFormat df = new SimpleDateFormat("yyMMddHH");
    String ticketDate = df.format(orderDate);
    Integer uid = getUID(ticketDate, ORDER_CODE);    

    return ticketDate + genRandom2(uid.toString(), 3, 3) ;
  }

Test,外部類調用非事務方法

  @Test
  public void testCreateGuid3(){
    String guid=this.keyService.genOrderCode(new Date());
    System.out.println(guid);
  }

測試結果居然是正常的,這與線上運行的結果相同,后面經同事提醒,這又是一個不正確使用事務的案例。

挖的第三個坑:當調用一個類的非事務方法且這個非事務方法內部調用了本類自身的事務方法,那么事務也不會生效。

問題二:下面的代碼可以實現事務回滾嗎?

Service

  • genOrderCode方法調用
  • getUID2兩個方法都是具備相同的事務參數
  • getUID2拋出異常
  • genOrderCode捕獲這個異常
@Transactional
  public Integer getUID2(String key, String type) {
    keyGeneratorDao.insert(key, type);
    Integer.parseInt("aaa");//throw exception
    keyGeneratorDao.update(key, type);
    return keyGeneratorDao.select(key, type);
  }

  @Transactional
  public String genOrderCode(Date orderDate)
  {
    try{
      SimpleDateFormat df = new SimpleDateFormat("yyMMddHH");
      String ticketDate = df.format(orderDate);
      Integer uid = getUID2(ticketDate, ORDER_CODE);    

      return ticketDate + genRandom2(uid.toString(), 3, 3) ;
    }catch(Exception ex){
      System.out.println(ex);
    }
    return null;
  }

Test

  @Test
  public void testCreateGuid3(){
    String guid=this.keyService.genOrderCode(new Date());
    System.out.println(guid);
  }

執行測試代碼,發現可以成功提交,但數據是不完整的,因為更新操作沒有完成。為什么會是這樣的呢?因為默認的Propagation.REQUIRED指明多個操作處于一個事務中,由于genOrderCode有異常處理,所以即使getUID2中發生異常,系統也會認定提交是合法的,因此會出現插入操作正常更新不正常但事務正常提交并不回滾的情況。
如果顯示指定Propagation.REQUIRES_NEW呢?

@Transactional(propagation=Propagation.REQUIRES_NEW)
  public Integer getUID2(String key, String type) {
    keyGeneratorDao.insert(key, type);
    Integer.parseInt("aaa");//throw exception
    keyGeneratorDao.update(key, type);
    return keyGeneratorDao.select(key, type);
  }

再執行相同的測試,數據正常回滾,這里提供兩張圖,可以看的清楚些(因為常用的就這兩種,其它的有興趣可以多多研究)

REQUIRED

Spring事務相關問題解決方案

REQUIRES_NEW

Spring事務相關問題解決方案

通過事務的兩個小問題,總結出解決問題的一些小技巧或者叫經驗:發現問題之后,不要局限于某個點,最好根據上下文來結合分析,比如問題一的readonly可寫入,單看那段代碼很難找出合理的解釋,只有結合前后端調用才能找出根本原因。寫單元測試盡量寫相同的代碼,否則有可能會出現一些干擾項影響判斷。學習呢,有時間盡量學的全點,比如@Transactional這個注解,除了readOnly還有Propagation等等。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

稻城县| 湛江市| 黄大仙区| 托克托县| 南安市| 托里县| 久治县| 漳浦县| 肃北| 乐安县| 上高县| 旅游| 潍坊市| 高台县| 青冈县| 兖州市| 忻州市| 肇庆市| 嘉禾县| 皋兰县| 广德县| 林州市| 监利县| 枣庄市| 德安县| 霍州市| 望都县| 巩留县| 威远县| 广汉市| 横峰县| 石狮市| 临汾市| 会理县| 铜山县| 丹巴县| 读书| 洪湖市| 习水县| 嵩明县| 肥西县|