一、MyBatis-Plus 1、簡介 MyBatis-Plus 是一個 Mybatis 增強版工具,在 MyBatis 上擴充了其他功能沒有改變其基本功能,為了簡化開發提交效率而存在。 官網文檔地址: https://mp.baomidou.com/guide/ MyBatis-Plus 特性: ...
一、MyBatis-Plus
1、簡介
MyBatis-Plus 是一個 Mybatis 增強版工具,在 MyBatis 上擴充了其他功能沒有改變其基本功能,為了簡化開發提交效率而存在。
官網文檔地址:
https://mp.baomidou.com/guide/
MyBatis-Plus 特性:
https://mp.baomidou.com/guide/#%E7%89%B9%E6%80%A7
2、使用 SpringBoot 快速使用 MyBatis-Plus
(1)準備工作
需要 Java 開發環境(JDK)以及相應的開發工具(IDE)。
需要 maven(用來下載相關依賴的 jar 包)。
需要 SpringBoot。
可以使用 IDEA 安裝一個 mybatis-plus 插件。
(2)創建一個 SpringBoot 項目。
方式一:去官網 https://start.spring.io/ 初始化一個,然後導入 IDE 工具即可。
方式二:直接使用 IDE 工具創建一個。 Spring Initializer。
(3)添加 MyBatis-Plus 依賴(mybatis-plus-boot-starter)
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1.tmp</version> </dependency>
(4)為了測試開發,此處使用 mysql 8,需要引入 mysql 相關依賴。
為了簡化代碼,引入 lombok 依賴(減少 getter、setter 等方法)。
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.18</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency>
(5)完整依賴文件(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lyh.test</groupId>
<artifactId>test-mybatis-plus</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>test-mybatis-plus</name>
<description>測試 -- 測試 MyBatis-Plus 功能</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(6)使用一個表進行測試。
僅供參考,可以定義 創建時間、修改時間等欄位。
DROP DATABASE IF EXISTS testMyBatisPlus; CREATE DATABASE testMyBatisPlus; USE testMyBatisPlus; DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT(20) 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 '郵箱', PRIMARY KEY (id) ); INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, '[email protected]'), (2, 'Jack', 20, '[email protected]'), (3, 'Tom', 28, '[email protected]'), (4, 'Sandy', 21, '[email protected]'), (5, 'Billie', 24, '[email protected]');
(7)在 application.yml 文件中配置 mysql 數據源信息。
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 url: jdbc:mysql://localhost:3306/testMyBatisPlus?useUnicode=true&characterEncoding=utf8
(8)編寫表對應的 實體類。
package entity; import lombok.Data; @Data public class User { private Long id; private String name; private int age; private String email; }
(9)編寫操作實體類的 Mapper 類。
直接繼承 BaseMapper,這是 mybatis-plus 封裝好的類。
package mapper; import bean.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface UserMapper extends BaseMapper<User> { }
(10)實體類、Mapper 類都寫好了,就可以使用了。
Step1:先得在啟動類里掃描 Mapper 類,即添加 @MapperScan 註解
package com.lyh.test.testmybatisplus; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @MapperScan("mapper") @SpringBootApplication public class TestMybatisPlusApplication { public static void main(String[] args) { SpringApplication.run(TestMybatisPlusApplication.class, args); } }
Step2:寫一個測試類測試一下。
package com.lyh.test.testmybatisplus; import bean.User; import mapper.UserMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class TestMybatisPlusApplicationTests { @Autowired private UserMapper userMapper; @Test public void testSelect() { System.out.println(("----- selectAll method test ------")); List<User> userList = userMapper.selectList(null); for(User user:userList) { System.out.println(user); } } }
(11)總結:
通過以上簡單操作,就能對 user 表進行 CRUD 操作,不需要去編寫 xml 文件。
註:
若遇到 @Autowired 標記的變數出現 紅色下劃線,但是不影響 正常運行。
可以進入 Settings,找到 Inspection,並選擇其中的 Spring Core -> Code -> Autowiring for Bean Class,將 Error 改為 Warning,即可。
二、Mybatis-Plus 常用操作
1、配置日誌
【參考地址(兩種方式配置日誌)】 https://blog.csdn.net/dfBeautifulLive/article/details/100700365
想要查看執行的 sql 語句,可以在 yml 文件中添加配置信息,如下。
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
如下圖所示:執行時會列印出 sql 語句。
2、簡單認識一下常用註解
【@TableName 】 @TableName 用於定義表名 註: 常用屬性: value 用於定義表名 【@TableId】 @TableId 用於定義表的主鍵 註: 常用屬性: value 用於定義主鍵欄位名 type 用於定義主鍵類型(主鍵策略 IdType) 主鍵策略: IdType.AUTO 主鍵自增,系統分配,不需要手動輸入 IdType.NONE 未設置主鍵 IdType.INPUT 需要自己輸入 主鍵值。 IdType.ASSIGN_ID 系統分配 ID,用於數值型數據(Long,對應 mysql 中 BIGINT 類型)。 IdType.ASSIGN_UUID 系統分配 UUID,用於字元串型數據(String,對應 mysql 中 varchar(32) 類型)。 【@TableField】 @TableField 用於定義表的非主鍵欄位。 註: 常用屬性: value 用於定義非主鍵欄位名 exist 用於指明是否為數據表的欄位, true 表示是,false 為不是。 fill 用於指定欄位填充策略(FieldFill)。 欄位填充策略:(一般用於填充 創建時間、修改時間等欄位) FieldFill.DEFAULT 預設不填充 FieldFill.INSERT 插入時填充 FieldFill.UPDATE 更新時填充 FieldFill.INSERT_UPDATE 插入、更新時填充。 【@TableLogic】 @TableLogic 用於定義表的欄位進行邏輯刪除(非物理刪除) 註: 常用屬性: value 用於定義未刪除時欄位的值 delval 用於定義刪除時欄位的值 【@Version】 @Version 用於欄位實現樂觀鎖
3、代碼生成器
(1)AutoGenerator 簡介
AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個模塊的代碼,極大的提升了開發效率。
與 mybatis 中的 mybatis-generator-core 類似。
(2)添加依賴
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.3.1.tmp</version> </dependency> <!-- 添加 模板引擎 依賴 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.2</version> </dependency>
(3)代碼分析
Step1:
創建一個 代碼生成器。用於生成代碼。
此處不用修改。
// Step1:代碼生成器 AutoGenerator mpg = new AutoGenerator();
Step2:
配置全局信息。指定代碼輸出路徑,以及包名、作者等信息。
此處按需添加,projectPath 需要修改,setAuthor 需要修改。
// Step2:全局配置 GlobalConfig gc = new GlobalConfig(); // 填寫代碼生成的目錄(需要修改) String projectPath = "E:\\myProject\\test\\test_mybatis_plus"; // 拼接出代碼最終輸出的目錄 gc.setOutputDir(projectPath + "/src/main/java"); // 配置開發者信息(可選)(需要修改) gc.setAuthor("lyh"); // 配置是否打開目錄,false 為不打開(可選) gc.setOpen(false); // 實體屬性 Swagger2 註解,添加 Swagger 依賴,開啟 Swagger2 模式(可選) //gc.setSwagger2(true); // 重新生成文件時是否覆蓋,false 表示不覆蓋(可選) gc.setFileOverride(false); // 配置主鍵生成策略,此處為 ASSIGN_ID(可選) gc.setIdType(IdType.ASSIGN_ID); // 配置日期類型,此處為 ONLY_DATE(可選) gc.setDateType(DateType.ONLY_DATE); // 預設生成的 service 會有 I 首碼 gc.setServiceName("%sService"); mpg.setGlobalConfig(gc);
Step3:
配置數據源信息。用於指定 需要生成代碼的 數據倉庫、數據表。
setUrl、setDriverName、setUsername、setPassword 均需修改。
// Step3:數據源配置(需要修改) DataSourceConfig dsc = new DataSourceConfig(); // 配置資料庫 url 地址 dsc.setUrl("jdbc:mysql://localhost:3306/testMyBatisPlus?useUnicode=true&characterEncoding=utf8"); // dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定資料庫名 // 配置資料庫驅動 dsc.setDriverName("com.mysql.cj.jdbc.Driver"); // 配置資料庫連接用戶名 dsc.setUsername("root"); // 配置資料庫連接密碼 dsc.setPassword("123456"); mpg.setDataSource(dsc);
Step4:
配置包信息。
setParent、setModuleName 均需修改。其餘按需求修改.
// Step:4:包配置 PackageConfig pc = new PackageConfig(); // 配置父包名(需要修改) pc.setParent("com.lyh.test"); // 配置模塊名(需要修改) pc.setModuleName("test_mybatis_plus"); // 配置 entity 包名 pc.setEntity("entity"); // 配置 mapper 包名 pc.setMapper("mapper"); // 配置 service 包名 pc.setService("service"); // 配置 controller 包名 pc.setController("controller"); mpg.setPackageInfo(pc);
Step5:
配置數據表映射信息。
setInclude 需要修改,其餘按實際開發修改。
// Step5:策略配置(資料庫表配置) StrategyConfig strategy = new StrategyConfig(); // 指定表名(可以同時操作多個表,使用 , 隔開)(需要修改) strategy.setInclude("test_mybatis_plus_user"); // 配置數據表與實體類名之間映射的策略 strategy.setNaming(NamingStrategy.underline_to_camel); // 配置數據表的欄位與實體類的屬性名之間映射的策略 strategy.setColumnNaming(NamingStrategy.underline_to_camel); // 配置 lombok 模式 strategy.setEntityLombokModel(true); // 配置 rest 風格的控制器(@RestController) strategy.setRestControllerStyle(true); // 配置駝峰轉連字元 strategy.setControllerMappingHyphenStyle(true); // 配置表首碼,生成實體時去除表首碼 // 此處的表名為 test_mybatis_plus_user,模塊名為 test_mybatis_plus,去除首碼後剩下為 user。 strategy.setTablePrefix(pc.getModuleName() + "_"); mpg.setStrategy(strategy);
Step6:
執行代碼生成操作。
此處不用修改。
// Step6:執行代碼生成操作 mpg.execute();
完整配置如下:
package com.lyh.test.test_mybatis_plus; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import org.junit.jupiter.api.Test; public class TestAutoGenerate { @Test public void autoGenerate() { // Step1:代碼生成器 AutoGenerator mpg = new AutoGenerator(); // Step2:全局配置 GlobalConfig gc = new GlobalConfig(); // 填寫代碼生成的目錄(需要修改) String projectPath = "E:\\myProject\\test\\test_mybatis_plus"; // 拼接出代碼最終輸出的目錄 gc.setOutputDir(projectPath + "/src/main/java"); // 配置開發者信息(可選)(需要修改) gc.setAuthor("lyh"); // 配置是否打開目錄,false 為不打開(可選) gc.setOpen(false); // 實體屬性 Swagger2 註解,添加 Swagger 依賴,開啟 Swagger2 模式(可選) //gc.setSwagger2(true); // 重新生成文件時是否覆蓋,false 表示不覆蓋(可選) gc.setFileOverride(false); // 配置主鍵生成策略,此處為 ASSIGN_ID(可選) gc.setIdType(IdType.ASSIGN_ID); // 配置日期類型,此處為 ONLY_DATE(可選) gc.setDateType(DateType.ONLY_DATE); // 預設生成的 service 會有 I 首碼 gc.setServiceName("%sService"); mpg.setGlobalConfig(gc); // Step3:數據源配置(需要修改) DataSourceConfig dsc = new DataSourceConfig(); // 配置資料庫 url 地址 dsc.setUrl("jdbc:mysql://localhost:3306/testMyBatisPlus?useUnicode=true&characterEncoding=utf8"); // dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定資料庫名 // 配置資料庫驅動 dsc.setDriverName("com.mysql.cj.jdbc.Driver"); // 配置資料庫連接用戶名 dsc.setUsername("root"); // 配置資料庫連接密碼 dsc.setPassword("123456"); mpg.setDataSource(dsc); // Step:4:包配置 PackageConfig pc = new PackageConfig(); // 配置父包名(需要修改) pc.setParent("com.lyh.test"); // 配置模塊名(需要修改) pc.setModuleName("test_mybatis_plus"); // 配置 entity 包名 pc.setEntity("entity"); // 配置 mapper 包名 pc.setMapper("mapper"); // 配置 service 包名 pc.setService("service"); // 配置 controller 包名 pc.setController("controller"); mpg.setPackageInfo(pc); // Step5:策略配置(資料庫表配置) StrategyConfig strategy = new StrategyConfig(); // 指定表名(可以同時操作多個表,使用 , 隔開)(需要修改) strategy.setInclude("test_mybatis_plus_user"); // 配置數據表與實體類名之間映射的策略 strategy.setNaming(NamingStrategy.underline_to_camel); // 配置數據表的欄位與實體類的屬性名之間映射的策略 strategy.setColumnNaming(NamingStrategy.underline_to_camel); // 配置 lombok 模式 strategy.setEntityLombokModel(true); // 配置 rest 風格的控制器(@RestController) strategy.setRestControllerStyle(true); // 配置駝峰轉連字元 strategy.setControllerMappingHyphenStyle(true); // 配置表首碼,生成實體時去除表首碼 // 此處的表名為 test_mybatis_plus_user,模塊名為 test_mybatis_plus,去除首碼後剩下為 user。 strategy.setTablePrefix(pc.getModuleName() + "_"); mpg.setStrategy(strategy); // Step6:執行代碼生成操作 mpg.execute(); } }
4、自動填充數據功能
(1)簡介
添加、修改數據時,每次都會使用相同的方式進行填充。比如 數據的創建時間、修改時間等。
Mybatis-plus 支持自動填充這些欄位的數據。
給之前的數據表新增兩個欄位:創建時間、修改時間。
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 '最後修改時間', PRIMARY KEY (id) );
並使用 代碼生成器生成代碼。
(2)未使用自動填充時
未使用 自動填充時,每次添加、修改數據都可以手動對其進行添加。
@SpringBootTest class TestMybatisPlusApplicationTests { @Autowired private UserService userService; @Test public void testUpdate() { User user = new User(); user.setName("tom").setAge(20).setEmail("[email protected]"); // 手動添加數據 user.setCreateTime(new Date()).setUpdateTime(new Date()); if (userService.save(user)) { userService.list().forEach(System.out::println); } else { System.out.println("添加數據失敗"); } } }
(3)使用自動填充功能。
Step1:
使用 @TableField 註解,標註需要進行填充的欄位。
/** * 創建時間 */ @TableField(fill = FieldFill.INSERT) private Date createTime; /** * 最後修改時間 */ @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;
Step2:
自定義一個類,實現 MetaObjectHandler 介面,並重寫方法。
添加 @Component 註解,交給 Spring 去管理。
package com.lyh.test.test_mybatis_plus.handler; 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 { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); this.strictInsertFill(metaObject, "updateTime", Date.class, new Date()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); } }
Step3:
簡單測試一下。
@Test public void testAutoFill() { User user = new User(); user.setName("tom").setAge(20).setEmail("[email protected]"); if (userService.save(user)) { userService.list().forEach(System.out::println); } else { System.out.println("添加數據失敗"); } }
5、邏輯刪除
(1)簡介
刪除數據,可以通過物理刪除,也可以通過邏輯刪除。
物理刪除指的是直接將數據從資料庫中刪除,不保留。
邏輯刪除指的是修改數據的某個欄位,使其表示為已刪除狀態,而非刪除數據,保留該數據在資料庫中,但是查詢時不顯示該數據(查詢時過濾掉該數據)。
給數據表增加一個欄位:delete_flag,用於表示該數據是否被邏輯刪除。
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 刪除)', PRIMARY KEY (id) );
(2)使用邏輯刪除。
可以定義一個自動填充規則,初始值為 0。0 表示未刪除, 1 表示刪除。
/** * 邏輯刪除(0 未刪除、1 刪除) */ @TableLogic(value = "0", delval = "1") @TableField(fill = FieldFill.INSERT) private Integer deleteFlag; @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "deleteFlag", Integer.class, 0); }
(3)簡單測試
使用 mybatis-plus 封裝好的方法時,會自動添加邏輯刪除的功能。
若是自定義的 sql 語句,需要手動添加邏輯。
@Test public void testDelete() { if (userService.removeById(1258924257048547329L)) { System.out.println("刪除數據成功"); userService.list().forEach(System.out::println); } else { System.out.println("刪除數據失敗"); } }
現有數據
執行 testDelete 進行邏輯刪除。
若去除 TableLogic 註解,再執行 testDelete 時進行物理刪除,直接刪除這條數據。
6、分頁插件的使用
(1)簡介
與 mybatis 的插件 pagehelper 用法類似。
通過簡單的配置即可使用。
(2)使用
Step1:
配置分頁插件。
編寫一個 配置類,內部使用 @Bean 註解將 PaginationInterceptor 交給 Spring 容器管理。
package com.lyh.test.test_mybatis_plus.config; import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 自定義一個配置類,mapper 掃描也可在此寫上 */ @Configuration @MapperScan("com.lyh.test.test_mybatis_plus.mapper") public class Myconfig { /** * 分頁插件 * @return 分頁插件的實例 */ @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }
Step2:
編寫分頁代碼。
直接 new 一個 Page 對象,對象需要傳遞兩個參數(當前頁,每頁顯示的條數)。
調用 mybatis-plus 提供的分頁查詢方法,其會將 分頁查詢的數據封裝到 Page 對象中。
@Test public void testPage() { // Step1:創建一個 Page 對象 Page<User> page = new Page<>(); // Page<User> page = new Page<>(2, 5); // Step2:調用 mybatis-plus 提供的分頁查詢方法 userService.page(page, null); // Step3:獲取分頁數據 System.out.println(page.getCurrent()); // 獲取當前頁 System.out.println(page.getTotal()); // 獲取總記錄數 System.out.println(page.getSize()); // 獲取每頁的條數 System.out.println(page.getRecords()); // 獲取每頁數據的集合 System.out.println(page.getPages()); // 獲取總頁數 System.out.println(page.hasNext()); // 是否存在下一頁 System.out.println(page.hasPrevious()); // 是否存在上一頁 }
7、樂觀鎖的實現
(1)首先認識一下 讀問題、寫問題?
操作資料庫數據時,遇到的最基本問題就是 讀問題與寫問題。
讀問題 指的是從資料庫中讀取數據時遇到的問題,比如:臟讀、幻讀、不可重覆讀。
【臟讀、幻讀、不可重覆讀 參考地址:】 https://www.cnblogs.com/l-y-h/p/12458777.html#_label0_3
寫問題 指的是數據寫入資料庫時遇到的問題,比如:丟失更新(多個線程同時對某條數據更新,無論執行順序如何,都會丟失其他線程更新的數據)
(2)如何解決寫問題?
樂觀鎖、悲觀鎖就是為瞭解決 寫問題而存在的。
樂觀鎖:總是假設最好的情況,每次讀取數據時認為數據不會被修改(即不加鎖),當進行更新操作時,會判斷這條數據是否被修改,未被修改,則進行更新操作。若被修改,則數據更新失敗,可以對數據進行重試(重新嘗試修改數據)。
悲觀鎖:總是假設最壞的情況,每次讀取數據時認為數據會被修改(即加鎖),當進行更新操作時,直接更新數據,結束操作後釋放鎖(此處才可以被其他線程讀取)。
(3)樂觀鎖、悲觀鎖使用場景?
樂觀鎖一般用於讀比較多的場合,儘量減少加鎖的開銷。
悲觀鎖一般用於寫比較多的場合,儘量減少 類似 樂觀鎖重試更新引起的性能開銷。
(4)樂觀鎖兩種實現方式
方式一:通過版本號機制實現。
在數據表中增加一個 version 欄位。
取數據時,獲取該欄位,更新時以該欄位為條件進行處理(即set version = newVersion where version = oldVersion),若 version 相同,則更新成功(給新 version 賦一個值,一般加 1)。若 version 不同,則更新失敗,可以重新嘗試更新操作。
方式二:通過 CAS 演算法實現。
CAS 為 Compare And Swap 的縮寫,即比較交換,是一種無鎖演算法(即在不加鎖的情況實現多線程之間的變數同步)。
CAS 操作包含三個操作數 —— 記憶體值(V)、預期原值(A)和新值(B)。如果記憶體地址裡面的值 V 和 A 的值是一樣的,那麼就將記憶體裡面的值更新成B。若 V 與 A 不一致,則不執行任何操作(可以通過自旋操作,不斷嘗試修改數據直至成功修改)。即 V == A ? V = B : V = V。
CAS 可能導致 ABA 問題(兩次讀取數據時值相同,但不確定值是否被修改過),比如兩個線程操作同一個變數,線程 A、線程B 初始讀取數據均為 A,後來 線程B 將數據修改為 B,然後又修改為 A,此時線程 A 再次讀取到的數據依舊是 A,雖然值相同但是中間被修改過,這就是 ABA 問題。可以加一個額外的標誌位 C,用於表示數據是否被修改。當標誌位 C 與預期標誌位相同、且 V == A 時,則更新值 B。
(5)mybatis-plus 實現樂觀鎖(通過 version 機制)
實現思路:
Step1:取出記錄時,獲取當前version
Step2:更新時,帶上這個version
Step3:執行更新時, set version = newVersion where version = oldVersion
Step4:如果version不對,就更新失敗
(6)mybatis-plus 代碼實現樂觀鎖
Step1:
配置樂觀鎖插件。
編寫一個配置類(可以與上例的分頁插件共用一個配置類),將 OptimisticLockerInterceptor 通過 @Bean 交給 Spring 管理。
/** * 樂觀鎖插件 * @return 樂觀鎖插件的實例 */ @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); }
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.upda