七,MyBatis-Plus 擴充套件功能:樂觀鎖,程式碼生成器,執行SQL分析列印(實操詳細使用)

Rainbow-Sea發表於2024-10-01

七,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. 總結:

  1. 注意:理解悲觀鎖和樂觀鎖:
    1. 悲觀鎖: 悲觀鎖是在查詢的時候就鎖定資料,在這次請求未完成之前,不會釋放鎖。必須等到這次請求執行完畢以後,再釋放掉鎖,釋放了鎖之後,其他請求才可以對於這條資料完成讀寫。
    2. 樂觀鎖: 樂觀鎖是透過表欄位完成設計的,他的核心思想是,在讀取的時候不加鎖,其他請求依然可以讀取到這個資料,在修改的時候判斷一個資料是否有被修改過,如果有被修改過,那本次請求的修改操作失敗。

5. 最後:

“在這個最後的篇章中,我要表達我對每一位讀者的感激之情。你們的關注和回覆是我創作的動力源泉,我從你們身上吸取了無盡的靈感與勇氣。我會將你們的鼓勵留在心底,繼續在其他的領域奮鬥。感謝你們,我們總會在某個時刻再次相遇。”

在這裡插入圖片描述

相關文章