徐媽教你使用 JPA 實現樂觀鎖

  • 2019 年 10 月 29 日
  • 筆記

來源:http://t.cn/EbM6Znt

  • 示例
  • 總結

樂觀鎖的概念就不再贅述了,不了解的朋友請自行百度Google之,今天主要說的是在項目中如何使用樂觀鎖,做成一個小demo。

持久層使用jpa時,默認提供了一個註解@Version先看看源碼怎麼描述這個註解的

@Target({ METHOD, FIELD })  @Retention(RUNTIME)  public @interface Version {  }  

簡單來說就是用一個version欄位來充當樂觀鎖的作用。

示例

先來設計實體類

/**   * Created by xujingfeng on 2017/1/30.   */  @Entity  @Table(name = "t_student")  public class Student {        @Id      @GenericGenerator(name = "PKUUID", strategy = "uuid2")      @GeneratedValue(generator = "PKUUID")      @Column(length = 36)      private String id;        @Version      private int version;        private String name;        //getter()...      //setter()...  }  

Dao層

/**   * Created by xujingfeng on 2017/1/30.   */  public interface StudentDao extends JpaRepository<Student,String>{        @Query("update Student set name=?1 where id=?2")      @Modifying      @Transactional      int updateNameById(String name,String id);  }  

Controller層充當單元測試的作用,通過訪問一個requestMapping來觸發我們想要測試的方法。

/**   * Created by xujingfeng on 2017/1/30.   */  @Controller  public class StudentController {        @Autowired      StudentDao studentDao;        @RequestMapping("student.html")      @ResponseBody      public String student(){          Student student = new Student();          student.setName("xujingfeng");          studentDao.save(student);          return "student";      }        @RequestMapping("testVersion.html")      @ResponseBody      public String testVersion() throws InterruptedException {          Student student = studentDao.findOne("6ed16acc-61df-4a66-add9-d17c88b69755");          student.setName("xuxuan");          new Thread(new Runnable() {              @Override              public void run() {                  studentDao.findOne("6ed16acc-61df-4a66-add9-d17c88b69755");                  student.setName("xuxuanInThread");                  studentDao.save(student);              }          }).start();          Thread.sleep(1000);          studentDao.save(student);          return "testVersion";      }          @RequestMapping("updateNameById.html")      @ResponseBody      public String updateNameById(){          studentDao.updateNameById("xuxuan2","6ed16acc-61df-4a66-add9-d17c88b69755");          return "updateNameById";      }      }  

這裡面三個方法,主要是我們想用來測試的三個注意點。 第一個方法student.html我們想看看springdata如何對version欄位進行增長的。就不貼圖了,直接給結論,對於添加了@Version的註解,我們不需要手動去控制,每一次save操作會在原來的基礎上+1,如果初始為null,則springdata自動設置其為0。 第二個方法testVersion.html是樂觀鎖的核心,當多個執行緒並發訪問同一行記錄時,添加了@Version樂觀鎖之後,程式會進行怎麼樣的控制呢?

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.example.jpa.Student#6ed16acc-61df-4a66-add9-d17c88b69755]  

異常資訊如上,主執行緒和新執行緒獲取了同一行記錄,並且新執行緒優先提交了事務,版本號一致,修改成功。等到了主執行緒再想save提交事務時,便得到一個版本號不一致的異常,那麼在項目開發中就應該自己捕獲這個異常根據業務內容做對應處理,是重試還是放棄etc…

第三個方法,updateNameById.html是想強調一下,@Query中的updatedelete操作是不會觸發springdata的相關代理操作的,而是轉化為原生sql的方式,所以在項目中使用時也要注意這點。

總結

樂觀鎖,用在一些敏感業務數據上,而其本身的修飾:樂觀,代表的含義便是相信大多數場景下version是一致的。但是從業務角度出發又要保證數據的嚴格一致性,避免臟讀等問題,使用的場景需要斟酌。記得前面一片博文簡單介紹了一下行級鎖的概念,其實本質上和樂觀鎖都是想要再資料庫層面加鎖控制並發,那麼什麼時候該用樂觀鎖,行級鎖,什麼時候得在程式級別加同步鎖,又要根據具體的業務場景去判斷。找到能夠滿足自己項目需求的方案,找到性能和可靠性的平衡點,才是一個程式設計師的價值所在。