樂觀鎖:總是假設最好的情況,每次讀取數據時認為數據不會被修改(即不加鎖),當進行更新操作時,會判斷這條數據是否被修改,未被修改,則進行更新操作。若被修改,則數據更新失敗,可以對數據進行重試(重新嘗試修改數據)。
悲觀鎖:總是假設最壞的情況,每次讀取數據時認為數據會被修改(即加鎖),當進行更新操作時,直接更新數據,結束操作后釋放鎖(此處才可以被其他線程讀取)。
樂觀鎖、悲觀鎖使用場景?
樂觀鎖一般用于讀比較多的場合,盡量減少加鎖的開銷。
悲觀鎖一般用于寫比較多的場合,盡量減少 類似 樂觀鎖重試更新引起的性能開銷。
樂觀鎖兩種實現方式
方式一:通過版本號機制實現。
在數據表中增加一個 version 字段。
取數據時,獲取該字段,更新時以該字段為條件進行處理(即set version = newVersion where version = oldVersion),若 version 相同,則更新成功(給新 version 賦一個值,一般加 1)。若 version 不同,則更新失敗,可以重新嘗試更新操作。
mybatis-plus 實現樂觀鎖(通過 version 機制)
實現思路:
Step1:取出記錄時,獲取當前version
Step2:更新時,帶上這個version
Step3:執行更新時, set version = newVersion where version = oldVersion
Step4:如果version不對,就更新失敗
mybatis-plus 代碼實現樂觀鎖
Step1:
配置樂觀鎖插件。
編寫一個配置類(可以與上例的分頁插件共用一個配置類),將 OptimisticLockerInterceptor 通過 @Bean 交給 Spring 管理。
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;@Configuration
// 配置掃描mapper的路徑
@MapperScan("com.mapper")
public class MyBatisPlusConfig {// 樂觀鎖插件// @Bean// public OptimisticLockerInterceptor optimisticLockerInterceptor() {// return new OptimisticLockerInterceptor();// }
}
Step2:
定義一個數據庫字段 version。
CREATE TABLE test_mybatis_plus_user
(id BIGINT NOT NULL COMMENT '主鍵ID',name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',age INT(11) NULL DEFAULT NULL COMMENT '年齡',email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',create_time timestamp NULL DEFAULT NULL COMMENT '創建時間',update_time timestamp NULL DEFAULT NULL COMMENT '最后修改時間', delete_flag tinyint(1) NULL DEFAULT NULL COMMENT '邏輯刪除(0 未刪除、1 刪除)',version int NULL DEFAULT NULL COMMENT '版本號(用于樂觀鎖, 默認為 1)',PRIMARY KEY (id)
);
?Step3:
使用 @Version 注解標注對應的實體類。
可以通過 @TableField 進行數據自動填充。
/*** 版本號(用于樂觀鎖, 默認為 1)*/
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;@Override
public void insertFill(MetaObject metaObject) {this.strictInsertFill(metaObject, "version", Integer.class, 1);
}
package com.config;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;import java.util.Date;@Component
public class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {// 方法1:在3.3.0+版本可以使用strictInsertFill()、strictUpdateFill()方法// 方法2:通用的自動填充方法this.setFieldValByName("gmtCreate", new Date(), metaObject);this.setFieldValByName("gmtModified", new Date(), metaObject);this.setFieldValByName("version", 0, metaObject); //新增就設置版本值為0}@Overridepublic void updateFill(MetaObject metaObject) {// 這里只有為空才會填充數據 所以修改一下// 方法1:在3.3.0+版本可以使用strictUpdateFill()方法try {if (metaObject.hasSetter("gmtModified")) {metaObject.setValue("gmtModified", null);}} catch (Exception e) {e.printStackTrace();}// 方法2:通用的自動填充方法this.setFieldValByName("gmtModified", new Date(), metaObject);}
}
Step4:
簡單測試一下,可以看一下 控制臺 打印的 sql 語句。?
@Test
public void testVersion() {User user = new User();user.setName("tom").setAge(20).setEmail("tom@163.com");userService.save(user);userService.list().forEach(System.out::println);user.setName("jarry");userService.update(user, null);userService.list().forEach(System.out::println);
}
?