使用JPA實現樂觀鎖

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

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

/**
 * Specifies the version field or property of an entity class that
 * serves as its optimistic lock value.  The version is used to ensure
 * integrity when performing the merge operation and for optimistic
 * concurrency control.
 *
 * <p> Only a single <code>Version</code> property or field
 * should be used per class; applications that use more than one
 * <code>Version</code> property or field will not be portable.
 *
 * <p> The <code>Version</code> property should be mapped to
 * the primary table for the entity class; applications that
 * map the <code>Version</code> property to a table other than
 * the primary table will not be portable.
 *
 * <p> The following types are supported for version properties:
 * <code>int</code>, <code>Integer</code>, <code>short</code>,
 * <code>Short</code>, <code>long</code>, <code>Long</code>,
 * <code>java.sql.Timestamp</code>.
 *
 * <pre>
 *    Example:
 *
 *    &#064;Version
 *    &#064;Column(name="OPTLOCK")
 *    protected int getVersionNum() { return versionNum; }
 * </pre>
 *
 * @since Java Persistence 1.0
 */
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
public @interface Version {
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

簡單來說就是用一個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()...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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";
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

這裏面三個方法,主要是我們想用來測試的三個注意點。
第一個方法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]
  • 1

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

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

總結

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

原文地址:http://blog.csdn.net/u013815546/article/details/54784025

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章