Mybatis-Plus學習筆記

目錄

一、MyBatis-Plus簡介

二、MyBatisPlus使用的基本流程:

(1)引入MybatisPlus依賴,代替MyBatis依賴

(2)自定義Mapper繼承BaseMapper

?編輯(3)在實體類上添加注解聲明表信息

(4)在application.yml中根據需要添加配置

三、核心功能

1、條件構造器

(1)QueryWrapper

(2)UpdateWrapper

(3)基于Lambda的Wrapper

2、自定義SQL(擅長處理where更新條件)

3、IService接口

4、LambdaQuery和LambdaUpdate

lambdaUpdate實現

5、批量新增 & 批處理方案性能測試

拓展:rewriteBatchedStatements=true 和 allowMultiQueries=true 的區別

@RequiredArgsConstructor注解

四、擴展功能

1、代碼生成

(1)安裝插件

(2)使用步驟

(3)代碼生成器配置

2、靜態工具

3、邏輯刪除

(1)介紹

注意:只有MybatisPlus生成的SQL語句才支持自動的邏輯刪除,自定義SQL需要自己手動處理邏輯刪除。

(2)@TableLogic

4、枚舉處理器

(1)定義枚舉,標記@EnumValue

(2)配置枚舉處理器

5、JSON類型處理器

(1)定義接收Json的實體類

(2)指定類型處理器

6、yaml配置加密

(1)生成密鑰????????

(2)修改配置

(3)配置密鑰運行參數

(4)實現原理

7、自動填充字段

(1)配置自動填充處理器

(2)添加@TableField的fill屬性

五、插件功能

1、分頁插件

(1)引入依賴

(2)配置分頁內置攔截器

(3)分頁API

2、通用分頁實體

(1)實體類設計

(2)開發接口

(3)改造PageDTO實體

(4)改造PageResult實體


一、MyBatis-Plus簡介


MyBatis-Plus 是一個 MyBatis 的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。

  • 支持的數據庫

  • 框架結構


MyBatis-Plus官網:https://baomidou.com/

參考文檔:https://mybatis.plus/ (網站訪問速度稍慢,建議直接看官網文檔)

二、MyBatisPlus使用的基本流程:
?

(1)引入MybatisPlus依賴,代替MyBatis依賴


MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且實現了自動裝配效果。

<!-- springboot2的mybatis-plus依賴 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.9</version>
</dependency>



注意:如果是springboot3,引入的是mybatis-plus-spring-boot3-starter依賴。

<!-- springboot3的mybatis-plus依賴 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.9</version>
</dependency>


(2)自定義Mapper繼承BaseMapper


(3)在實體類上添加注解聲明表信息

MybatisPlus底層通過反射,根據PO實體的信息來推斷出表的信息,從而生成SQL的。默認情況下(約定):

  • MybatisPlus會把PO實體的類名駝峰轉下劃線作為表名
  • MybatisPlus會把PO實體的所有變量名駝峰轉下劃線作為表的字段名,并根據變量類型推斷字段類型
  • MybatisPlus會把名為id的字段作為主鍵

但很多情況下,默認的實現與實際場景不符(實際情況與MP的約定不符合時使用),因此MybatisPlus提供了一些注解便于我們聲明表信息。

  • @TableName:用來指定表名
  • @Tableld:用來指定表中的主鍵字段信息
  • @TableField:用來指定表中的普通字段信息

常見注解:

(4)在application.yml中根據需要添加配置

常見配置:

三、核心功能

1、條件構造器

除了新增以外,修改、刪除、查詢的SQL語句都需要指定where條件。因此BaseMapper中提供的相關方法除了以id作為where條件以外,還支持更加復雜的where條件。

參數中的Wrapper就是條件構造的抽象類,其下有很多默認實現,繼承關系如圖:

Wrapper的子類AbstractWrapper提供了where中包含的所有條件構造方法:

而QueryWrapper在AbstractWrapper的基礎上拓展了一個select方法,允許指定查詢字段:

而UpdateWrapper在AbstractWrapper的基礎上拓展了一個set方法,允許指定SQL中的SET部分:

(1)QueryWrapper

無論是修改、刪除、查詢,都可以使用QueryWrapper來構建查詢條件。

示例:

// 查詢出名字中帶o的,存款大于等于1000元的人的id、username、info、balance字段
@Test
void testQueryWrapper() {// 1.構建查詢條件 where username like "%o%" AND balance >= 1000QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.select("id", "username", "info", "balance").like("username", "o").ge("balance", 1000);// 2.查詢數據List<User> userList = userMapper.selectList(queryWrapper);userList.forEach(System.out::println);
}
// 更新用戶名為jack的用戶的余額為2000。
@Test
void testUpdateByQueryWrapper() {// 1.設置要更新的數據User user = new User();user.setBalance(2000);// 2.構建更新條件 where username = "Jack"QueryWrapper<User> queryWrapper = new QueryWrapper<User>().eq("username", "Jack");// 3.執行更新,user中非null字段都會作為set語句System.out.println(userMapper.update(user, queryWrapper) > 0);
}

(2)UpdateWrapper

基于BaseMapper中的update方法更新時只能直接賦值,對于一些復雜的需求就難以實現。

示例:

@Test
void testUpdateWrapper() {List<Long> ids = List.of(1L, 2L, 4L);// 1.生成SQLUpdateWrapper<User> updateWrapper = new UpdateWrapper<User>().setSql("balance = balance - 200")  // SET balance = balance - 200.in("id", ids); // WHERE id in (1, 2, 4)// 2.基于UpdateWrapper中的setSql來更新System.out.println(userMapper.update(updateWrapper) > 0);
}

(3)基于Lambda的Wrapper
  • LambdaQueryWrapper,對應QueryWrapper
  • LambdaUpdateWrapper,對應UpdateWrapper

示例:

@Test
void testLambdaUpdateWrapper() {List<Long> ids = List.of(1L, 2L, 4L);// 1.生成SQLLambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<User>().setSql("balance = balance - 200")  // SET balance = balance - 200.in(User::getId, ids); // WHERE id in (1, 2, 4)// 2.基于UpdateWrapper中的setSql來更新System.out.println(userMapper.update(lambdaUpdateWrapper) > 0);
}@Test
void testLambdaQueryWrapper() {// 1.構建查詢條件 where username like "%o%" AND balance >= 1000LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.select(User::getId, User::getUsername, User::getInfo, User::getBalance).like(User::getUsername, "o").ge(User::getBalance, 1000);// 2.查詢數據List<User> userList = userMapper.selectList(queryWrapper);userList.forEach(System.out::println);
}

總結:

  • QueryWrapper和LambdaQueryWrapper通常用來構建select、delete、update的where條件部分
  • UpdateWrapper和LambdaUpdateWrapper通常只有在set語句比較特殊的情況才使用
  • 盡量使用LambdaQueryWrapper和LambdaUpdateWrapper避免硬編碼

2、自定義SQL(擅長處理where更新條件)

  • 問題引出

把Mapper層的sql語句寫在Service層了,這在某些企業也是不允許的,因為SQL語句最好都維護在持久層,而不是業務層。

示例:

@Test
void testCustomSQLUpdate() {// 更新條件List<Long> ids = List.of(1L, 2L, 4L);int amount = 200;// 定義條件LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, ids);// 調用自定義SQL方法userMapper.updateBalanceByIds(wrapper, amount);
}
  • ${ew.customSqlSegment}:為自定義SQL片段,@Param("ew")其中參數ew必須叫這個,如果忘記了也可以用baomidou包下的常量類Constants.WRAPPER,其值等于"ew"

3、IService接口

MybatisPlus不僅提供了BaseMapper,還提供了通用的Service接口及默認實現,封裝了一些常用的service模板方法。

繼承+實現

由于Service中經常需要定義與業務有關的自定義方法,因此我們不能直接使用IService,而是自定義Service接口,然后繼承MP的IService接口以拓展方法。讓自定義的ServiceImpl實現類實現自定義的Service接口,同時繼承MP的默認實現類 ServiceImpl,同時,這樣就不用自己實現IService接口中的方法了。

代碼示例:

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;public interface IUserService extends IService<User> {
}import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.mapper.UserMapper;
import com.itheima.mp.service.IUserService;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}

注意:ServiceImpl<M, T>接口的泛型參數中,M是繼承了BaseMapper的Mapper接口,T是PO實體類。

4、LambdaQuery和LambdaUpdate

IService中還提供了Lambda功能來簡化我們的復雜查詢及更新功能。

代碼示例:

public interface IUserService extends IService<User> {List<User> queryUserByCondition(UserQueryDTO queryDTO);
}// 基于Lambda查詢
@Override
public List<User> queryUserByCondition(UserQueryDTO queryDTO) {String name = queryDTO.getName();Integer status = queryDTO.getStatus();Integer minBalance = queryDTO.getMinBalance();Integer maxBalance = queryDTO.getMaxBalance();return lambdaQuery().like(name != null, User::getUsername, name).eq(status != null, User::getStatus, status).ge(minBalance != null, User::getBalance, minBalance).le(maxBalance != null, User::getBalance, maxBalance).list();
}

MP對LambdaQueryWrapper和LambdaUpdateWrapper的用法進一步做了簡化。我們無需自己通過new的方式來創建Wrapper,而是直接調用lambdaQuery和lambdaUpdate方法。在組織查詢條件的時候,我們加入了name != null這樣的參數,意思就是當條件成立時才會添加這個查詢條件,類似Mybatis的mapper.xml文件中的<if>標簽。這樣就實現了動態查詢條件效果了。

MybatisPlus會根據鏈式編程的最后一個方法來判斷最終的返回結果。lambdaQuery方法中除了可以構建條件,還需要在鏈式編程的最后添加一個查詢結果,list()表示查詢結果返回一個List集合。可選的常用方法有:

  • one():最多1個結果
  • list():返回集合結果
  • count():返回計數結果
  • exist():返回查詢的結果是否存在

lambdaUpdate實現

代碼示例:

@Override
@Transactional
public void deductBalanceById(Long id, Integer money) {// 查詢用戶User user = getById(id);// 校驗用戶狀態if (user == null || user.getStatus() == 2) {throw new RuntimeException("用戶狀態異常!");}// 校驗余額是否充足if (user.getBalance() < money) {throw new RuntimeException("用戶余額不足!");}// 扣減余額//baseMapper.deductBalance(id, money);int remainBalance = user.getBalance() - money;lambdaUpdate().set(User::getBalance, remainBalance)   // 更新余額.set(remainBalance == 0, User::getStatus, 2)    // 動態判斷是否更新status.eq(User::getBalance, user.getBalance())    // CAS樂觀鎖.eq(User::getId, id)   // 根據id扣減對應用戶的余額.update();  // 注意:LambdaUpdate做復雜更新時,最后必須記得加上.update()進行更新操作
}

5、批量新增 & 批處理方案性能測試

需求:批量插入10萬條用戶數據,并作出對比。

  • 方式一:普通for循環逐條插入
  • 方式二:IService的批量插入(默認不開啟 jdbc 批處理參數)
  • 方式三:開啟rewriteBatchedStatements=true參數

方式一:執行結果耗時大約為551.9秒

/*** 10w次插入意味著10w次網絡請求,耗時最慢*/
@Test
void testSaveOneByOne() {long b = System.currentTimeMillis();for (int i = 1; i <= 100000; i++) {userService.save(buildUser(i));}long e = System.currentTimeMillis();System.out.println("耗時:" + (e - b));
}private User buildUser(int i) {User user = new User();user.setUsername("user_" + i);user.setPassword("123");user.setPhone("" + (18688190000L + i));user.setBalance(2000);user.setInfo("{\"age\": 24, \"intro\": \"英文老師\", \"gender\": \"female\"}");user.setCreateTime(LocalDateTime.now());user.setUpdateTime(user.getCreateTime());return user;
}

方式二:執行結果耗時大約為27.6秒,打包逐條插入,從網絡請求層面大大減少了耗時。

/*** MP批處理采用的是JDBC底層的預編譯方案PreparedStatement,將1000條數據統一打包執行save一并提交到MySQL,每1000條發送一次網絡請求,插入100次共發送100次網絡請求* MP如果不加JDBC連接參數rewriteBatchedStatements=true,底層還是打包逐條插入,只不過是從網絡請求數量上減少了耗時* 而加上了MySQL的這個開啟批處理參數后,MP調用的JDBC底層的批處理才能真正變成一次性批量插入多條數據*/
@Test
void testSaveBatch() {// 因為一次性new 10萬條數據占用內存太多,并且向數據庫請求的數據包有上限大小限制(一次網絡傳輸的數據量是有限的)// 所以我們每次批量插入1000條件,插入100次即10萬條數據// 準備一個容量為1000的集合List<User> list = new ArrayList<>(1000);long b = System.currentTimeMillis();for (int i = 1; i <= 100000; i++) {// 添加一個userlist.add(buildUser(i));// 每1000條批量插入一次if (i % 1000 == 0) {// 批量插入userService.saveBatch(list);// 清空集合,準備下一批數據list.clear();}}long e = System.currentTimeMillis();System.out.println("耗時:" + (e - b));
}

方式三:開啟參數后,測試耗時大約為6.5秒

利用MP的jdbc批處理,只不過MySQL本身默認沒有開啟這個批處理參數rewriteBatchedStatements=true,該參數在MySQL 3.1.13版本開始引入,默認值為false不開啟。

spring:datasource:url: jdbc:mysql://127.0.0.1:3307/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=truedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456

批處理方案總結:

  • 普通for循環逐條插入速度極差,不推薦
  • MP的批量新增,基于預編譯的批處理,性能不錯
  • 配置jdbc參數,開啟rewriteBatchedStatements,性能最好
拓展:rewriteBatchedStatements=true 和 allowMultiQueries=true 的區別
  • rewriteBatchedStatements是重寫sql語句達到發送一次sql的請求效果;allowMultiQueries是在mapper.xml中使用;分號分隔多條sql,允許多條語句一起發送執行的。
  • 對于insert批處理操作,開啟rewriteBatchedStatements=true,驅動則會把多條sql語句重寫成一條sql語句然后再發出去;而對于update和delete批處理操作,開啟allowMultiQueries=true,驅動所做的事就是把多條sql語句累積起來再一次性發出去。

@RequiredArgsConstructor注解

@RequiredArgsConstructor?是?Project Lombok?庫提供的一個注解,它用于在編譯時自動生成一個包含所有?“必需”字段?的構造函數。

哪些字段被認為是“必需的”?

  1. 所有被?final?關鍵字修飾的字段:這些字段必須在對象創建時被初始化。

  2. 所有被?@NonNull?注解標記且未初始化的字段:這些字段不能為?null,因此也必須在構造函數中初始化。

對于普通的、非 final 且沒有?@NonNull?約束的字段,不會被包含在這個生成的構造函數中。

四、擴展功能

1、代碼生成

(1)安裝插件

在idea的plugins市場中搜索并安裝MyBatisPlus插件:

(2)使用步驟

我們利用插件生成一下。 首先需要配置數據庫地址,在Idea頂部菜單中,找到other,選擇Config Database,在彈出的窗口中填寫數據庫連接的基本信息:

點擊OK保存。然后再次點擊Idea頂部菜單中的other,然后選擇Code Generator,在彈出的表單中填寫信息:

最終,代碼自動生成到指定的位置了

(3)代碼生成器配置

如果不想用圖形化界面方式配置生成代碼,使用MyBatis-Plus官網提供的代碼生成器模板也是可以的,但需要自己填寫配置信息。

因為MP代碼生成更新迭代速度很快,若本文的API被棄用,請以官網最新版本API為準:

MyBatis-Plus新代碼生成器:https://baomidou.com/guides/new-code-generator/

代碼生成器配置:https://baomidou.com/reference/new-code-generator-configuration/

  • 引入依賴
<!-- MP代碼生成器 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.9</version>
</dependency>
  • 代碼生成模板配置示例
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;import java.util.Collections;
import java.util.List;public class CodeGenerator {/*** 數據庫鏈接地址**/private static final String JDBC_URL_MAN = "jdbc:mysql://xxxxx:3306/xxx?useUnicode=true&characterEncoding=UTF-8";/*** 數據庫登錄賬號**/private static final String JDBC_USER_NAME = "xx";/*** 數據庫登錄密碼**/private static final String JDBC_PASSWORD = "xxxx";public static void main(String[] args) {String dir = "\\xx\\xxx";String tablePrefix = "tb_";List<String> tables = List.of("tb_user");FastAutoGenerator.create(JDBC_URL_MAN, JDBC_USER_NAME, JDBC_PASSWORD).globalConfig(builder -> {builder.author("Aizen")                                                           // 作者.outputDir(System.getProperty("user.dir") + dir + "\\src\\main\\java")    // 輸出路徑(寫到java目錄).enableSwagger()                                                          // 開啟swagger.commentDate("yyyy-MM-dd");                                       // 設置注釋日期格式,默認值: yyyy-MM-dd}).packageConfig(builder -> {builder.parent("com.{company}")     // 設置父包名.moduleName("{model}")      // 設置父包模塊名.entity("domain")           // 設置實體類包名.service("service")         // 設置Service接口包名.serviceImpl("service.impl")// 設置Service實現類包名.controller("controller")   // 設置Controller包名.mapper("mapper")           // 設置Mapper接口文件包名.xml("mappers")             // 設置Mapper XML文件包名.pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + dir + "\\src\\main\\resources\\mapper"));}).strategyConfig(builder -> {builder.addInclude(tables)                              // 設置需要生成的表名.addTablePrefix(tablePrefix)                    // 設置表前綴.serviceBuilder()                               // 設置 Service 層模板.formatServiceFileName("%sService").formatServiceImplFileName("%sServiceImpl").entityBuilder()                                // 設置實體類模板.enableLombok()                                 // 啟用 Lombok.logicDeleteColumnName("deleted")               // 邏輯刪除字段名(數據庫字段).enableTableFieldAnnotation()                   // 開啟生成實體時生成字段注解.controllerBuilder().formatFileName("%sController").enableRestStyle()                              // 啟用 REST 風格.mapperBuilder()                                // Mapper 策略配置.enableBaseResultMap()                          // 生成通用的resultMap.superClass(BaseMapper.class)                   // 設置父類.formatMapperFileName("%sMapper").enableMapperAnnotation().formatXmlFileName("%sMapper");})//.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默認的是Velocity引擎模板.execute();     // 執行生成}
}

2、靜態工具

有的時候Service之間也會相互調用,為了避免出現循環依賴問題,MybatisPlus提供一個靜態工具類:Db,其中的一些靜態方法與IService中方法簽名基本一致,也可以幫助我們實現CRUD功能:

因為靜態方法無法讀取類上的泛型,所以MP在使用靜態工具讀取表信息時,需要傳入PO實體類的Class字節碼,MP再通過反射獲取到表信息。其中新增和修改的方法由于需要傳入實體類對象,因此不用傳入實體類的Class字節碼。

代碼示例:

@Test
void testDbGet() {User user = Db.getById(1L, User.class);System.out.println(user);
}@Test
void testDbList() {// 利用Db實現復雜條件查詢List<User> list = Db.lambdaQuery(User.class).like(User::getUsername, "o").ge(User::getBalance, 1000).list();list.forEach(System.out::println);
}@Test
void testDbUpdate() {Db.lambdaUpdate(User.class).set(User::getBalance, 2000).eq(User::getUsername, "Rose");
}

3、邏輯刪除

(1)介紹

對于一些比較重要的數據,我們往往會采用邏輯刪除的方案,即:

  • 在表中添加一個字段標記數據是否被刪除,邏輯刪除字段的屬性通常是IntegerBoolean類型。
  • 當刪除數據時把標記置為1,1表示已刪除
  • 查詢時只查詢標記為0的數據,0表示未刪除

同理更新操作也需要加上deleted = 0,所以一旦采用了邏輯刪除,所有的查詢、刪除、更新邏輯都要跟著變化,非常麻煩。

為了解決這個問題,MybatisPlus就添加了對邏輯刪除的支持。無需改變方法調用的方式,而是在底層幫我們自動修改CRUD的語句。我們只需要在application.yaml文件中配置邏輯刪除的字段名稱和值即可。
?

注意:只有MybatisPlus生成的SQL語句才支持自動的邏輯刪除,自定義SQL需要自己手動處理邏輯刪除。

代碼示例:

@SpringBootTest
class IAddressServiceTest {@Autowiredprivate IAddressService addressService;@Testvoid testLogicDelete() {// 刪除addressService.removeById(59L);// 查詢Address address = addressService.getById(59L);System.out.println("address = " + address);}
}

(2)@TableLogic

  • @TableLogic注解用于標記實體類中的邏輯刪除字段。
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("address")
public class Address implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "id", type = IdType.AUTO)private Long id;// 省略.../*** 邏輯刪除*/@TableLogic // @TableLogic注解用于標記實體類中的邏輯刪除字段//@TableLogic(value = "0", delval = "1") // value表示默認邏輯未刪除值,delval表示默認邏輯刪除值 (這兩個值可無、會自動獲取全局配置)@TableField("deleted")private Boolean deleted;
}

使用這種方式就不用在application.yaml中配置MP邏輯刪除字段了,直接在邏輯刪除字段屬性上加該注解即可。

總結:邏輯刪除本身也有自己的問題,比如

  • 會導致數據庫表垃圾數據越來越多,從而影響查詢效率
  • SQL中全都需要對邏輯刪除字段做判斷,影響查詢效率

因此,不太推薦采用邏輯刪除功能,如果數據不能刪除,可以采用把數據遷移到其它表的辦法。

實際業務應該有回收站的邏輯處理數據

4、枚舉處理器

當實體類屬性是枚舉類型,在與數據庫的字段類型做轉換時,底層默認使用的是MyBatis提供的EnumOrdinalTypeHandler枚舉類型處理器。

但是這個并不好用,所以MP對類型處理器做了增強,其中增強后的枚舉處理器叫MybatisEnumTypeHandler,JSON處理器叫AbstractJsonTypeHandler

(1)定義枚舉,標記@EnumValue

代碼示例:

import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;@Getter
public enum UserStatus {NORMAL(1, "正常"),FREEZE(2, "凍結");@EnumValueprivate final int value;private final String desc;UserStatus(int value, String desc) {this.value = value;this.desc = desc;}
}

要讓MybatisPlus處理枚舉與數據庫類型自動轉換,我們必須告訴MybatisPlus,枚舉中的哪個字段的值作為數據庫值。?MybatisPlus提供了@EnumValue注解來標記枚舉屬性值。因此我們需要給枚舉中與數據庫字段類型對應的屬性值添加@EnumValue注解。

(2)配置枚舉處理器

在application.yaml文件中配置枚舉處理器

mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

如果我們想向前端返回指定的枚舉屬性,例如value狀態值desc描述,SpringMVC負責處理響應數據,它在底層處理Json時用的是jackson,所以我們只需要使用jackson提供的@JsonValue注解,來標記JSON序列化后展示的字段。

代碼示例:

import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;@Getter
public enum UserStatus {NORMAL(1, "正常"),FREEZE(2, "凍結");@EnumValueprivate final int value;@JsonValueprivate final String desc;UserStatus(int value, String desc) {this.value = value;this.desc = desc;}
}

效果:

5、JSON類型處理器

數據庫的user表中有一個info字段,是JSON類型

這樣一來,我們要讀取info中的屬性時就非常不方便。如果要方便獲取,info的類型最好是一個Map或者實體類。而一旦我們把info改為對象類型,就需要在寫入數據庫時手動轉為String,再讀取數據庫時,手動轉換為對象,這會非常麻煩。

因此MybatisPlus提供了很多特殊類型字段的類型處理器,解決特殊字段類型與數據庫類型轉換的問題。例如處理JSON就可以使用JacksonTypeHandler處理器(SpringMVC底層默認也是使用的這個類)。

(1)定義接收Json的實體類

首先定義一個單獨實體類UserInfo來與info字段的屬性匹配

@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")  // 為了方便構建對象,為有參構造提供靜態方法,名為of,UserInfo.of()
public class UserInfo {private Integer age;private String intro;private String gender;
}

(2)指定類型處理器


將User和UserVO類的info字段修改為UserInfo類型,并聲明類型處理器@TableField(typeHandler = JacksonTypeHandler.class)。另外,將info改為對象類型后出現對象嵌套,在復雜嵌套查詢時需要使用resultMap結果集映射,否則無法映射。所以還需要再@TableName注解中添加autoResultMap=true確保能夠正常映射。

/*** 詳細信息*/@TableField(typeHandler = JacksonTypeHandler.class)private UserInfo info;

如果啟動mapper.xml報錯,在info字段后加上, typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler就解決了。

效果:

6、yaml配置加密

目前我們配置文件中的很多參數都是明文,如果開發人員發生流動,很容易導致敏感信息的泄露。所以MybatisPlus支持配置文件的加密和解密功能。

以數據庫的用戶名和密碼為例:

(1)生成密鑰????????

首先,我們利用MP提供的AES工具生成一個隨機秘鑰,然后對用戶名、密碼加密。

import com.baomidou.mybatisplus.core.toolkit.AES;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
class MpDemoApplicationTest {public static final String USERNAME = "root";public static final String PASSWORD = "123456";@Testvoid testEncrypt() {// 生成 16 位隨機 AES 密鑰String randomKey = AES.generateRandomKey();System.out.println("randomKey = " + randomKey); // randomKey = 7pSEa6F9TnYacTNJ// 利用密鑰對用戶名加密String username = AES.encrypt(USERNAME, randomKey);System.out.println("username = " + username);   // username = O4Yq+WKYGlPW5t8QvgrhUQ==// 利用密鑰對用戶名加密String password = AES.encrypt(PASSWORD, randomKey);System.out.println("password = " + password);   // password = cDYHnWysq07zUIAy1tcbRQ==}
}
(2)修改配置

修改application.yaml文件,把jdbc的用戶名、密碼修改為剛剛加密生成的密文。

spring:datasource:url: jdbc:mysql://127.0.0.1:3307/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=truedriver-class-name: com.mysql.cj.jdbc.Driverusername: mpw:O4Yq+WKYGlPW5t8QvgrhUQ== # 密文要以 mpw:開頭password: mpw:cDYHnWysq07zUIAy1tcbRQ== # 密文要以 mpw:開頭
(3)配置密鑰運行參數

在啟動項目的時候,需要把剛才生成的AES秘鑰添加到Jar啟動參數中,像這樣:

--mpw.key=7pSEa6F9TnYacTNJ,新版本idea添加Program arguments中設置,界面如下

單元測試的時候不能添加啟動參數,所以要在測試類的注解上配置:@SpringBootTest(args = "--mpw.key=7pSEa6F9TnYacTNJ")

(4)實現原理

SpringBoot提供修改Spring環境后置處理器【EnvironmentPostProcessor】,允許在應用程序之前操作環境屬性值,MyBatisPlus對其進行了重寫實現。

package com.baomidou.mybatisplus.autoconfigure;import com.baomidou.mybatisplus.core.toolkit.AES;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource;import java.util.HashMap;/*** 安全加密處理器** @author hubin* @since 2020-05-23*/
public class SafetyEncryptProcessor implements EnvironmentPostProcessor {@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {/*** 命令行中獲取密鑰*/String mpwKey = null;for (PropertySource<?> ps : environment.getPropertySources()) {if (ps instanceof SimpleCommandLinePropertySource) {SimpleCommandLinePropertySource source = (SimpleCommandLinePropertySource) ps;mpwKey = source.getProperty("mpw.key");break;}}/*** 處理加密內容*/if (StringUtils.isNotBlank(mpwKey)) {HashMap<String, Object> map = new HashMap<>();for (PropertySource<?> ps : environment.getPropertySources()) {if (ps instanceof OriginTrackedMapPropertySource) {OriginTrackedMapPropertySource source = (OriginTrackedMapPropertySource) ps;for (String name : source.getPropertyNames()) {Object value = source.getProperty(name);if (value instanceof String) {String str = (String) value;if (str.startsWith("mpw:")) {map.put(name, AES.decrypt(str.substring(4), mpwKey));}}}}}// 將解密的數據放入環境變量,并處于第一優先級上if (CollectionUtils.isNotEmpty(map)) {environment.getPropertySources().addFirst(new MapPropertySource("custom-encrypt", map));}}}
}

7、自動填充字段

MyBatis-Plus提供了一個便捷的自動填充功能,用于在插入或更新數據時自動填充某些字段,如創建時間、更新時間等。

(1)配置自動填充處理器

自動填充功能通過實現 com.baomidou.mybatisplus.core.handlers.MetaObjectHandler 接口來實現。我們需要創建一個類來實現這個接口,并在其中定義插入和更新時的填充邏輯。添加@Component配置自動填充處理器類被Spring管理。

  • MyMetaObjectHandler實現MetaObjectHandler接口
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {log.info("開始插入填充...");// 起始版本 3.3.3(推薦)this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());// Date類型填充//this.strictInsertFill(metaObject, "createTime", () -> new Date(), Date.class);// 起始版本 3.3.0(推薦使用)//this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());// 也可以使用(3.3.0 該方法有bug)//this.fillStrategy(metaObject, "createTime", LocalDateTime.now());}@Overridepublic void updateFill(MetaObject metaObject) {log.info("開始更新填充...");// 起始版本 3.3.3(推薦)this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());// Date類型填充//this.strictUpdateFill(metaObject, "updateTime", Date::new, Date.class);// 起始版本 3.3.0(推薦)//this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());// 也可以使用(3.3.0 該方法有bug)//this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 該方法有bug)}
}

(2)添加@TableField的fill屬性

在實體類中,你需要使用?@TableField?注解來標記哪些字段需要自動填充,并通過fill屬性指定填充的策略。

@Data
@TableName(value = "user", autoResultMap = true)
public class User {// 省略.../*** 創建時間*/@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;/*** 更新時間*/@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;
}

注意事項:

  • 自動填充是直接給實體類的屬性設置值,如果屬性沒有值,入庫時會是null。
  • MetaObjectHandler 提供的默認方法策略是:如果屬性有值則不覆蓋,如果填充值為 null 則不填充。
  • 字段必須聲明 @TableField 注解,并設置 fill 屬性來選擇填充策略。
  • 在 update(T entity, Wrapper<T> updateWrapper) 時,entity 不能為空,否則自動填充失效。
  • 在 update(Wrapper<T> updateWrapper) 時不會自動填充,需要手動賦值字段條件。
  • 使用 strictInsertFill 或 strictUpdateFill 方法可以根據注解 FieldFill.xxx、字段名和字段類型來區分填充邏輯。如果不需區分,可以使用 fillStrategy 方法。

五、插件功能

MybatisPlus提供了很多的插件功能,進一步拓展其功能。目前已有的插件有:

  • PaginationInnerInterceptor:自動分頁
  • TenantLineInnerInterceptor:多租戶
  • DynamicTableNameInnerInterceptor:動態表名
  • OptimisticLockerInnerInterceptor:樂觀鎖
  • IllegalSQLInnerInterceptor:sql 性能規范
  • BlockAttackInnerInterceptor:防止全表更新與刪除

注意:使用多個分頁插件的時候需要注意插件定義順序,建議使用順序如下:

  • 多租戶,動態表名
  • 分頁,樂觀鎖
  • sql 性能規范,防止全表更新與刪除

1、分頁插件

在未引入分頁插件的情況下,MybatisPlus是不支持分頁功能的,IServiceBaseMapper中的分頁方法都無法正常起效。 所以,我們必須配置分頁插件。

(1)引入依賴

? 注意,MyBatisPlus于 v3.5.9 起,PaginationInnerInterceptor已分離出來。如需使用,則需單獨引入mybatis-plus-jsqlparser依賴!

<!-- MP分頁插件 jdk 11+ 引入可選模塊 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-jsqlparser</artifactId><version>3.5.9</version>
</dependency>
<!-- MP分頁插件 jdk 8+ 引入可選模塊 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
</dependency>

(2)配置分頁內置攔截器

新建一個配置類MyBatisConfig

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** MyBatis配置類*/
@Configuration
public class MybatisConfig {/*** MP攔截器*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {// 初始化核心插件MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加分頁插件PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);paginationInnerInterceptor.setMaxLimit(1000L);  // 設置單頁分頁條數最大限制interceptor.addInnerInterceptor(paginationInnerInterceptor);return interceptor;}
}

(3)分頁API

代碼示例:

@Test
void testPageQuery() {// 準備分頁條件int pageNo = 1, pageSize = 2;   // 頁碼、每頁查詢條數// 查詢條件:無// 分頁條件Page<User> page = Page.of(pageNo, pageSize);// 排序條件//page.addOrder(new OrderItem("id", true)); // MP老版本排序條件寫法,true為升序,false為降序page.addOrder(OrderItem.desc("balance"));   // 新版MP直接用OrderItem的靜態方法page.addOrder(OrderItem.asc("id")); // 可以添加多個排序條件// 分頁查詢page = userService.page(page);  // 這里返回的page對象其實和上面是同一個地址,只不過是封裝好分頁查詢結果的page對象// 獲取page中的查詢結果long total = page.getTotal();   // 總條數System.out.println("total = " + total);long pages = page.getPages();   // 總頁數System.out.println("pages = " + pages);List<User> records = page.getRecords(); // 分頁數據records.forEach(System.out::println);
}

效果:

2、通用分頁實體

現在要實現一個用戶分頁查詢的接口,接口規范如下:

這里需要用到4個實體:

  • PageDTO:通用分頁查詢條件的實體,包含分頁頁碼、每頁查詢條數、排序字段、是否升序
  • UserQueryDTO:接收用戶查詢條件實體,為了實現分頁功能去繼承PageDTO
  • PageResult:分頁結果實體,包含總條數、總頁數、當前頁數據
  • UserVO:響應用戶頁面視圖實體,將用戶VO集合封裝到PageResult中返回

(1)實體類設計

分頁條件不僅僅用戶分頁查詢需要,以后其它業務也都有分頁查詢的需求。因此建議將分頁查詢條件單獨定義為一個PageDTO實體

  • PageDTO 通用分頁查詢條件的實體
@Data
@ApiModel(description = "通用分頁查詢實體")
public class PageDTO {@ApiModelProperty("頁碼")private Integer pageNo;@ApiModelProperty("每頁查詢條數")private Integer pageSize;@ApiModelProperty("排序字段")private String sortBy;@ApiModelProperty("是否升序")private Boolean isAsc;
}

  • UserQueryDTO 接收用戶查詢條件實體,繼承PageDTO
@EqualsAndHashCode(callSuper = true)    // 當判斷相等時先考慮父類屬性再考慮子類屬性,就是分頁的時候把分頁條件作為數據請求的的前提,然后再考慮查到了哪些匹配的數據
@Data
@ApiModel(description = "用戶查詢條件實體")
public class UserQueryDTO extends PageDTO {@ApiModelProperty("用戶名關鍵字")private String name;@ApiModelProperty("用戶狀態:1-正常,2-凍結")private Integer status;@ApiModelProperty("余額最小值")private Integer minBalance;@ApiModelProperty("余額最大值")private Integer maxBalance;
}
  • PageResult 分頁結果實體
@Data
@ApiModel(description = "分頁結果")
public class PageResult<T> {@ApiModelProperty("總條數")private Long total;@ApiModelProperty("總頁數")private Long pages;@ApiModelProperty("結果集合")private List<T> list;
}
  • UserVO 響應用戶頁面視圖實體,將用戶VO集合封裝到PageResult中返回,之前已經定義過了
@Data
@ApiModel(description = "用戶VO實體")
public class UserVO {@ApiModelProperty("用戶id")private Long id;@ApiModelProperty("用戶名")private String username;@ApiModelProperty("詳細信息")private UserInfo info;@ApiModelProperty("使用狀態(1正常 2凍結)")private UserStatus status;@ApiModelProperty("賬戶余額")private Integer balance;@ApiModelProperty("用戶的收獲地址")private List<AddressVO> addresses;
}

(2)開發接口

Controller層:UserController

// in UserController
@ApiOperation("根據條件分頁查詢用戶接口")
@GetMapping("/condition/page")
public PageResult<UserVO> queryUserByConditionAndPage(UserQueryDTO queryDTO) {return userService.queryUserByConditionAndPage(queryDTO);
}

Service層:IUserService

// in IUserService
PageResult<UserVO> queryUserByConditionAndPage(UserQueryDTO queryDTO);// in UserServiceImpl
@Override
public PageResult<UserVO> queryUserByConditionAndPage(UserQueryDTO queryDTO) {// 構建分頁條件Page<User> page = Page.of(queryDTO.getPageNo(), queryDTO.getPageSize());// 構建排序條件if (StrUtil.isNotBlank(queryDTO.getSortBy())) { // 如果排序字段不為空page.addOrder(new OrderItem().setColumn(queryDTO.getSortBy()).setAsc(queryDTO.getIsAsc()));}else { // 如果排序字段為空,默認按照更新時間排序page.addOrder(new OrderItem().setColumn("update_time").setAsc(false));}// 分頁查詢String name = queryDTO.getName();Integer status = queryDTO.getStatus();Page<User> p = lambdaQuery().like(name != null, User::getUsername, name).eq(status != null, User::getStatus, status).page(page);// 封裝VO結果PageResult<UserVO> result = new PageResult<>();result.setTotal(p.getTotal());  // 總條數result.setPages(p.getPages());  // 總頁數// 當前頁數據List<User> records = p.getRecords();// 其實也可以不用判斷,因為如果查到的是空集合,轉換完還是空集合,不影響最后的結果if (CollUtil.isEmpty(records)) {result.setList(Collections.emptyList());return result;}// 將用戶集合拷貝為用戶VO集合List<UserVO> userVOList = BeanUtil.copyToList(records, UserVO.class);result.setList(userVOList);return result;
}

(3)改造PageDTO實體

在剛才的代碼中,從PageDTOMybatisPlusPage之間轉換的過程還是比較麻煩的。

對于PageDTO構建為MP的分頁對象的部分,我們完全可以在PageDTO這個實體內部中定義一個轉換方法,簡化開發。

一般是封裝成工具類進行操作

import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;@Data
@ApiModel(description = "通用分頁查詢實體")
public class PageDTO {@ApiModelProperty("頁碼")private Integer pageNo;@ApiModelProperty("每頁查詢條數")private Integer pageSize;@ApiModelProperty("排序字段")private String sortBy;@ApiModelProperty("是否升序")private Boolean isAsc;/*** 在PageDTO內部將PageDTO構建為MP的分頁對象,并設置排序條件* @param items 排序條件(可以有一個或多個)* @return MP的分頁對象* @param <T> MP的分頁對象的泛型*/public <T> Page<T> toMpPage(OrderItem... items) {// 構建分頁條件Page<T> page = Page.of(pageNo, pageSize);// 構建排序條件if (sortBy != null && sortBy.trim().length() > 0) { // 如果排序字段不為空page.addOrder(new OrderItem().setColumn(sortBy).setAsc(isAsc));}else if (items != null) { // 如果排序字段為空,且傳入的默認OrderItem不為空,按照調用者傳入的默認OrderItem排序page.addOrder(items);}return page;}/*** 將PageDTO構建為MP的分頁對象* 如果調用者不想new OrderItem對象,可以調用該方法,傳入默認的排序字段和排序規則即可* @param defaultSortBy 排序字段* @param defaultAsc 排序規則,true為asc,false為desc* @return MP的分頁對象* @param <T> MP的分頁對象的泛型*/public <T> Page<T> toMpPage(String defaultSortBy, Boolean defaultAsc) {return toMpPage(new OrderItem().setColumn(defaultSortBy).setAsc(defaultAsc));}/*** 將PageDTO構建為MP的分頁對象,排序條件按update_time更新時間降序* @return MP的分頁對象* @param <T> MP的分頁對象的泛型*/public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {return toMpPage("update_time", false);}/*** 將PageDTO構建為MP的分頁對象,排序條件按create_time創建時間降序* @return MP的分頁對象* @param <T> MP的分頁對象的泛型*/public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {return toMpPage("create_time", false);}
}

(4)改造PageResult實體

在查詢出分頁結果后,數據的非空校驗,數據的VO轉換都是模板代碼,編寫起來很麻煩。

我們完全可以將?PO分頁對象轉換為VO分頁結果對象?的邏輯,封裝到?PageResult?的內部方法中,簡化整個過程。

相當于定義通用業務工具類

@Data
@ApiModel(description = "分頁結果")
public class PageResult<T> {@ApiModelProperty("總條數")private Long total;@ApiModelProperty("總頁數")private Long pages;@ApiModelProperty("結果集合")private List<T> list;/*** 將PO分頁對象轉換為VO分頁結果對象* @param page PO分頁對象* @param clazz 目標VO的字節碼對象* @return VO分頁結果對象* @param <PO> PO實體* @param <VO> VO實體*/public static <PO, VO> PageResult<VO> of(Page<PO> page, Class<VO> clazz) {// PO分頁對象封裝為VO結果PageResult<VO> result = new PageResult<>();result.setTotal(page.getTotal());  // 總條數result.setPages(page.getPages());  // 總頁數List<PO> records = page.getRecords();    // 當前頁數據// 其實也可以不用判斷,因為如果查到的是空集合,轉換完還是空集合,不影響最后的結果if (CollUtil.isEmpty(records)) {result.setList(Collections.emptyList());return result;}// 將PO集合拷貝為VO集合List<VO> userVOList = BeanUtil.copyToList(records, clazz);result.setList(userVOList);return result;}/*** 將PO分頁對象轉換為VO分頁結果對象* @param page PO分頁對象* @param convertor 自定義規則轉換器* @return VO分頁結果對象* @param <PO> PO實體* @param <VO> VO實體*/public static <PO, VO> PageResult<VO> of(Page<PO> page, Function<PO, VO> convertor) {// PO分頁對象封裝為VO結果PageResult<VO> result = new PageResult<>();result.setTotal(page.getTotal());  // 總條數result.setPages(page.getPages());  // 總頁數List<PO> records = page.getRecords();    // 當前頁數據// 其實也可以不用判斷,因為如果查到的是空集合,轉換完還是空集合,不影響最后的結果if (CollUtil.isEmpty(records)) {result.setList(Collections.emptyList());return result;}// 將PO集合轉換為VO集合,轉換動作由調用者來傳遞List<VO> voList = records.stream().map(convertor).collect(Collectors.toList());result.setList(voList);return result;}
}

最終Service層代碼:

@Override
public PageResult<UserVO> queryUserByConditionAndPage(UserQueryDTO queryDTO) {// 構建分頁條件對象Page<User> page = queryDTO.toMpPageDefaultSortByUpdateTimeDesc();// 分頁查詢String name = queryDTO.getName();Integer status = queryDTO.getStatus();Page<User> p = lambdaQuery().like(name != null, User::getUsername, name).eq(status != null, User::getStatus, status).page(page);// PO分頁對象封裝為VO結果return PageResult.of(p, UserVO.class);
}

如果是希望自定義PO到VO的轉換過程,可以調用重載方法of(Page<PO> page, Function<PO, VO> convertor),convertor的轉換器邏輯由調用者去編寫傳遞:

@Override
public PageResult<UserVO> queryUserByConditionAndPage(UserQueryDTO queryDTO) {// 構建分頁條件對象Page<User> page = queryDTO.toMpPageDefaultSortByUpdateTimeDesc();// 分頁查詢String name = queryDTO.getName();Integer status = queryDTO.getStatus();Page<User> p = lambdaQuery().like(name != null, User::getUsername, name).eq(status != null, User::getStatus, status).page(page);// PO分頁對象封裝為VO結果return PageResult.of(p, user -> {// PO拷貝基礎屬性得到VOUserVO userVO = BeanUtil.copyProperties(user, UserVO.class);// 對VO進行處理特殊邏輯String username = userVO.getUsername();// 例如用戶名脫敏處理userVO.setUsername(username.length() > 2 ? StrUtil.fillAfter(username.substring(0, 2), '*', username.length()) : username.charAt(0) + "*");return userVO;});
}

自定義轉換規則的場景,例如:

  • ① PO字段和VO字段不是包含關系,出現字段不一致。
  • ② 對VO中的屬性做一些過濾、數據脫敏、加密等操作。
  • ③ 將VO中的屬性繼續設置數據,例如VO中的address屬性,可以查詢出用戶所屬的收獲地址,設置后一并返回。

效果:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/web/96602.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/96602.shtml
英文地址,請注明出處:http://en.pswp.cn/web/96602.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Day22 用C語言編譯應用程序

文章目錄1. 保護操作系統5&#xff08;harib19a&#xff09;2. 幫助發現bug&#xff08;harib19b&#xff09;3. 強制結束應用程序&#xff08;harib19c&#xff09;4. 用C語言顯示字符串&#xff08;harib19e&#xff09;5. 顯示窗口&#xff08;harib19f&#xff09;1. 保護操…

簡單學習HTML+CSS+JavaScript

一、HTML HTML被稱為 超文本標記語言&#xff0c;是由一系列標簽構成的語言。 下面介紹HTML中的標簽&#xff1a; &#xff08;一&#xff09;HTML文件基本結構 <!DOCTYPE html><html><head><title>Document</title></head> <body&…

強化學習中重要性采樣

PPO 中重要性采樣 https://github.com/modelscope/ms-swift/blob/main/docs/source/Instruction/GRPO/GetStarted/GRPO.md樂&#xff0c;這個網頁中是的groundtruth是錯誤的&#xff08;可能是為了防止抄襲&#xff09;。一些例子 0. 池塘養魚的一個例子 想象一下&#xff0c;你…

《樹與二叉樹詳解:概念、結構及應用》

目錄 一. 樹的概念和結構 1.1 樹的基本概念 1.2 樹的結構特點 二. 樹的表示方法和實際運用 2.1 孩子 - 兄弟表示法&#xff08;Child-Sibling Representation&#xff09; 2.2 樹的實際應用場景 三. 二叉樹的概念 3.1 二叉樹的核心定義 3.2 二叉樹的基本分類 四. 二叉…

Qt/C++,windows多進程demo

1. 項目概述 最近研究了一下Qt/C框架下&#xff0c;windows版本的多進程編寫方法&#xff0c;實現了一個小demo。下面詳細介紹一下。 MultiProcessDemo是一個基于Qt框架實現的多進程應用程序示例&#xff0c;展示了如何在Windows平臺上通過共享內存和事件機制實現進程間通信。該…

Android SystemServer 系列專題【篇五:UserController用戶狀態控制】

本篇接著SystemServer的啟動流程&#xff0c;圍繞SystemServer最后階段關于主用戶的啟動和解鎖的流程&#xff0c;作為切入點&#xff0c;來看看SystemServer是如何講用戶狀態同步到所有的系統級服務中。ssm.onStartUserssm.onUnlockingUserssm.onUnlockedUser本篇先介紹UserCo…

推薦使用 pnpm 而不是 npm

npm 的局限性 磁盤空間浪費在 npm 早期版本中&#xff0c;每個項目的node_modules目錄都會完整復制所有依賴包&#xff0c;即使多個項目依賴同一個包的相同版本&#xff0c;也會重復存儲。這導致磁盤空間被大量占用&#xff0c;隨著項目數量的增加&#xff0c;存儲成本顯著上升…

Transformer實戰(18)——微調Transformer語言模型進行回歸分析

Transformer實戰&#xff08;18&#xff09;——微調Transformer語言模型進行回歸分析0. 前言1. 回歸模型2. 數據處理3. 模型構建與訓練4. 模型推理小結系列鏈接0. 前言 在自然語言處理領域中&#xff0c;預訓練 Transformer 模型不僅能勝任離散類別預測&#xff0c;也可用于連…

【Linux】【實戰向】Linux 進程替換避坑指南:從理解 bash 阻塞等待,到親手實現能執行 ls/cd 的 Shell

前言&#xff1a;歡迎各位光臨本博客&#xff0c;這里小編帶你直接手撕&#xff0c;文章并不復雜&#xff0c;愿諸君耐其心性&#xff0c;忘卻雜塵&#xff0c;道有所長&#xff01;&#xff01;&#xff01;&#xff01; IF’Maxue&#xff1a;個人主頁&#x1f525; 個人專欄…

linux常用命令 (3)——系統包管理

博客主頁&#xff1a;christine-rr-CSDN博客 ????? ?? hi&#xff0c;大家好&#xff0c;我是christine-rr ! 今天來分享一下linux常用命令——系統包管理 目錄linux常用命令---系統包管理&#xff08;一&#xff09;Debian 系發行版&#xff08;Ubuntu、Debian、Linux …

YOLOv8 mac-intel芯片 部署指南

&#x1f680; 在 Jupyter Notebook 和 PyCharm 中使用 Conda 虛擬環境&#xff08;YOLOv8 部署指南&#xff0c;Python 3.9&#xff09; YOLOv8 是 Ultralytics 開源的最新目標檢測模型&#xff0c;輕量高效&#xff0c;支持分類、檢測、分割等多種任務。 在 Mac&#xff08;…

【高等數學】第十一章 曲線積分與曲面積分——第六節 高斯公式 通量與散度

上一節&#xff1a;【高等數學】第十一章 曲線積分與曲面積分——第五節 對坐標的曲面積分 總目錄&#xff1a;【高等數學】 目錄 文章目錄1. 高斯公式2. 沿任意閉曲面的曲面積分為零的條件3. 通量與散度1. 高斯公式 設空間區域ΩΩΩ是由分片光滑的閉曲面ΣΣΣ所圍成&#x…

IDEA試用過期,無法登錄,重置方法

IDEA過期&#xff0c;重置方法: IntelliJ IDEA 2024.2.0.2 (親測有效) 最新Idea重置辦法!&#xff1a; 方法一&#xff1a; 1、刪除C:\Users\{用戶名}\AppData\Local\JetBrains\IntelliJIdea2024.2 下所有文件(注意&#xff1a;是子目錄全部刪除) 2、刪除C:\Users\{用戶名}\App…

創建用戶自定義橋接網絡并連接容器

1.創建用戶自定義的 alpine-net 網絡[roothost1 ~]# docker network create --driver bridge alpine-net 9f6d634e6bd7327163a9d83023e435da6d61bc6cf04c9d96001d1b64eefe4a712.列出 Docker 主機上的網絡[roothost1 ~]# docker network ls NETWORK ID NAME DRIVER …

Vue3 + Vite + Element Plus web轉為 Electron 應用,解決無法登錄、隱藏自定義導航欄

如何在vue3 Vite Element Plus搭好的架構下轉為 electron應用呢&#xff1f; https://www.electronjs.org/zh/docs/latest/官方文檔 https://www.electronjs.org/zh/docs/latest/ 第一步&#xff1a;安裝 electron相關依賴 npm install electron electron-builder concurr…

qt QAreaLegendMarker詳解

1. 概述QAreaLegendMarker 是 Qt Charts 模塊中的一部分&#xff0c;用于在圖例&#xff08;Legend&#xff09;中表示 QAreaSeries 的標記。它負責顯示區域圖的圖例項&#xff0c;通常包含區域顏色樣例和對應的描述文字。圖例標記和對應的區域圖關聯&#xff0c;顯示區域的名稱…

linux 函數 kstrtoul

kstrtoul 函數概述 kstrtoul 是 Linux 內核中的一個函數&#xff0c;用于將字符串轉換為無符號長整型&#xff08;unsigned long&#xff09;。該函數定義在 <linux/kernel.h> 頭文件中&#xff0c;常用于內核模塊中解析用戶空間傳遞的字符串參數。 函數原型 int kstrtou…

LLM(三)

一、人類反饋的強化學習&#xff08;RLHF&#xff09;微調的目標是通過指令&#xff0c;包括路徑方法&#xff0c;進一步訓練你的模型&#xff0c;使他們更好地理解人類的提示&#xff0c;并生成更像人類的回應。RLHF&#xff1a;使用人類反饋微調型語言模型&#xff0c;使用強…

DPO vs PPO,偏好優化的兩條技術路徑

1. 背景在大模型對齊&#xff08;alignment&#xff09;里&#xff0c;常見的兩類方法是&#xff1a;PPO&#xff1a;強化學習經典算法&#xff0c;OpenAI 在 RLHF 里用它來“用獎勵模型更新策略”。DPO&#xff1a;2023 年提出的新方法&#xff08;參考論文《Direct Preferenc…

BLE6.0信道探測,如何重構物聯網設備的距離感知邏輯?

在物聯網&#xff08;IoT&#xff09;無線通信技術快速滲透的當下&#xff0c;實現人與物、物與物之間對物理距離的感知響應能力已成為提升設備智能高度與人們交互體驗的關鍵所在。當智能冰箱感知用戶靠近而主動亮屏顯示內部果蔬時、當門禁系統感知到授權人士靠近而主動開門時、…