持久層使用jpa時,默認提供了一個注解@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中的update,delete操作是不會觸發springdata的相關代理操作的,而是轉化為原生sql的方式,所以在項目中使用時也要注意這點。
總結
樂觀鎖,用在一些敏感業務數據上,而其本身的修飾:樂觀,代表的含義便是相信大多數場景下version是一致的。但是從業務角度出發又要保證數據的嚴格一致性,避免臟讀等問題,使用的場景需要斟酌。記得前面一片博文簡單介紹了一下行級鎖的概念,其實本質上和樂觀鎖都是想要再數據庫層面加鎖控制並發,那么什么時候該用樂觀鎖,行級鎖,什么時候得在程序級別加同步鎖,又要根據具體的業務場景去判斷。找到能夠滿足自己項目需求的方案,找到性能和可靠性的平衡點,才是一個程序員的價值所在。
出處:http://www.importnew.com/26099.html
