七,MyBatis-Plus 擴充套件功能:樂觀鎖,程式碼生成器,執行SQL分析列印(實操詳細使用)
@
- 七,MyBatis-Plus 擴充套件功能:樂觀鎖,程式碼生成器,執行SQL分析列印(實操詳細使用)
- 1. 樂觀鎖
- 2. 程式碼生成器
- 3. 執行SQL分析列印
- 4. 總結:
- 5. 最後:
1. 樂觀鎖
首先我們需要先了解開發中的一個常見場景,叫做併發請求。
併發請求就是在同一時刻有多個請求,同時請求伺服器資源,如果是獲取資訊,沒什麼問題,但是如果是對於資訊做修改操作,那就會出現問題。
這裡我們舉一個例子。比如:目前商品的庫存只剩餘 1件了,這個時候有多個使用者都想要購買這件商品,都發起了購買商品的請求,那麼能讓這多個使用者都購買到麼,肯定是不行的,因為多個使用者都買到了這件商品,那麼就會出現超賣問題,庫存不夠時沒法發貨的。所以在開發中就要解決這種超賣的問題。
拋開超賣的這一種場景,諸如此類併發訪問的場景非常多,這類場景的核心問題就是,一個請求在執行的過程中,其他請求不能改變資料,如果是一次完整的請求,在該請求的過程中其他請求沒有對於這個資料產生修改操作,那麼這個請求時能夠正常修改資料的。如果該請求在改變資料的過程中,已經有其他請求改變了資料,那該請求就不去改變這條資料了。
想要解決這類問題,最常見的就是加鎖的思想,鎖可以用驗證在請求的執行過程中,是否有資料發生改變。
常見的資料庫鎖型別有兩種,悲觀鎖和樂觀鎖。
一次完成的修改操作是:先查詢資料,然後修改資料。
這樣做的操作能夠保證讀取到的資訊就是當前的資訊,保證了資訊的正確性,但是併發效率很低,在實際開發中使用悲觀鎖的場景很少,因為在併發時,我們是要保證效率的。
樂觀鎖: 樂觀鎖是透過表欄位完成設計的,他的核心思想是,在讀取的時候不加鎖,其他請求依然可以讀取到這個資料,在修改的時候判斷一個資料是否有被修改過,如果有被修改過,那本次請求的修改操作失敗。
具體的透過 SQL 是這樣實現的,新增了一個 where version = 1
這樣做的操作是不會對於資料讀取產生影響,併發的效率較高,但是可能目前看到的資料並不是真實資訊資料,是被修改之前的,但是在很多場景下是可以容忍的,並不是產生很大影響。例如:很多時候我們看到的是有庫存,或者都加入都購物車,但是點進去以後庫存沒有了。
在資料庫表中新增一個欄位 version,表示版本,預設值是1
生成後的效果
找到實體類,新增對應的屬性,並使用 @Version標註
為這是一個樂觀鎖欄位資訊。
因為要對每條修改語句完成語句的增強,這裡我們透過攔截器的配置,讓每條修改的 sql
語句在執行的時候,都加上版本控制的功能。
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
/*
透過配置類來指定一個具體資料庫的分頁外掛,因為不同的資料庫的方言不同,具
體澀會給你從的分頁語句也會不同,這裡我們指定資料庫為 MySQL資料庫
*/
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 樂觀鎖
return mybatisPlusInterceptor;
}
}
測試效果,這裡我們模擬先查詢,再修改
@Test
void updateTest(){
User user = userMapper.selectById(6L);
user.setName("li");
userMapper.updateById(user);
}
我們透過檢視拼接好的SQL語句發現,查詢時將User的資料查詢出來,是包含version版本資訊的
當我們完成修改時,他會將版本號 + 1
此時檢視資料發現,更改姓名後,version已經為2了
接下來我們模擬一下,當出現多個修改請求的時候,是否能夠做到樂觀鎖的效果。
樂觀鎖的效果是,一個請求在修改的過程中,是允許另一個請求查詢的,但是修改時會透過版本號是否改變來決定是否修改,如果版本號變了,證明已經有請求修改過資料了,那這次修改不生效,如果版本號沒有發生變化,那就完成修改。
package com.rainbowsea;
import com.rainbowsea.bean.User;
import com.rainbowsea.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class LockTest {
@Autowired
private UserMapper userMapper;
@Test
void updateTest2() {
// 模擬操作1的查詢操作
User user1 = userMapper.selectById("5");
System.out.println("查詢結果:" + user1);
// 模擬操作2的查詢操作
User user2 = userMapper.selectById("5");
System.out.println("查詢結果:" + user2);
// 模擬操作2的修改操作
user2.setName("liHua");
userMapper.updateById(user2);
// 模擬操作1的修改操作
user1.setName("zhangsan");
userMapper.updateById(user1);
}
}
我們來看下這段程式碼的執行過程,這段程式碼其實是兩次操作,只不過操作1在執行的過程中,有操作2完成了對於資料的修改,這時操作1就無法再次進行修改了
操作1的查詢:此時版本為2
操作2的查詢:此時版本為2
操作2的修改:此時檢查版本,版本沒有變化,所以完成修改,並將版本改為3
操作1的修改:此時檢查版本,版本已經有最初獲取的版本資訊發生了變化,所以杜絕修改
2. 程式碼生成器
程式碼生成器和逆向工程的區別在於,程式碼生成器可以生成更多的結構,更多的內容,允許我們能夠配置生成的選項更多。在這裡我們演示一下程式碼生成器的用法。
參考官網,使用程式碼生成器需要引入兩個依賴;
<!-- mybatis-plus 的依賴-->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<!--freemarker模板依賴-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
編寫程式碼生成器程式碼
@SpringBootTest
class GeneratorApplicationTests {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false", "root", "root")
.globalConfig(builder -> {
builder.author("powernode") // 設定作者
//.enableSwagger() // 開啟 swagger 模式
.fileOverride() // 覆蓋已生成檔案
.outputDir("D://"); // 指定輸出目錄
})
.packageConfig(builder -> {
builder.parent("com.powernode") // 設定父包名
.moduleName("mybatisplus") // 設定父包模組名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://")); // 設定mapperXml生成路徑
})
.strategyConfig(builder -> {
builder.addInclude("powershop_user") // 設定需要生成的表名
.addTablePrefix("powershop"); // 設定過濾表字首
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,預設的是Velocity引擎模板
.execute();
}
}
執行,檢視生成效果
3. 執行SQL分析列印
在我們日常開發工作當中,避免不了檢視當前程式所執行的SQL語句,以及瞭解它的執行時間,方便分析是否出現了慢SQL問題。我們可以使用MybatisPlus提供的SQL分析列印的功能,來獲取SQL語句執行的時間。
由於該功能依賴於 p6spy
元件,所以需要在 pom.xml
中先引入該元件。
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
在 application.yml
中進行配置
將驅動和 url
修改
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql
在 resources
下,建立 spy.properties
配置檔案。
#3.2.1以上使用modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定義日誌列印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日誌輸出到控制檯
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日誌系統記錄 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 設定 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL字首
useprefix=true
# 配置記錄 Log 例外,可去掉的結果集error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 實際驅動可多個
#driverlist=org.h2.Driver
# 是否開啟慢SQL記錄
outagedetection=true
# 慢SQL記錄標準 2 秒
outagedetectioninterval=2
測試
執行查詢所有的操作,可以看到sql語句的執行時間
4. 總結:
- 注意:理解悲觀鎖和樂觀鎖:
- 悲觀鎖: 悲觀鎖是在查詢的時候就鎖定資料,在這次請求未完成之前,不會釋放鎖。必須等到這次請求執行完畢以後,再釋放掉鎖,釋放了鎖之後,其他請求才可以對於這條資料完成讀寫。
- 樂觀鎖: 樂觀鎖是透過表欄位完成設計的,他的核心思想是,在讀取的時候不加鎖,其他請求依然可以讀取到這個資料,在修改的時候判斷一個資料是否有被修改過,如果有被修改過,那本次請求的修改操作失敗。
5. 最後:
“在這個最後的篇章中,我要表達我對每一位讀者的感激之情。你們的關注和回覆是我創作的動力源泉,我從你們身上吸取了無盡的靈感與勇氣。我會將你們的鼓勵留在心底,繼續在其他的領域奮鬥。感謝你們,我們總會在某個時刻再次相遇。”