無知的我正在複盤MybatisPlus,順便上傳筆記。。。
下圖是我總結的 MP 知識的初級思維導圖,後續會不斷補充
文章目錄
-
- 1 MyBatisPlus入門案例與簡介
-
- 1.1 入門案例
-
- 回顧SpringBoot整合Mybatis的開發過程:
- 步驟1:建立資料庫及表
- 步驟2:建立SpringBoot工程
- 步驟3:勾選配置使用技術
- 步驟4:pom.xml補全依賴
- 步驟5:添加MP的相關配置資訊
- 步驟6:根據資料庫表建立實體類
- 步驟7:建立Dao接口
- 步驟8:編寫引導類
- 步驟9:編寫測試類
- 1.2 MybatisPlus簡介
- 2 标準資料層開發
-
- 2.1 标準CRUD的MP方法
- 2.2 新增
- 2.3 删除
- 2.4 修改
- 2.5 根據ID查詢
- 2.6 查詢所有
- 2.7 Lombok
-
- 使用步驟
-
- 步驟1:添加lombok依賴
- 步驟2:安裝Lombok的插件
- 步驟3:模型類上添加注解
- 2.8 分頁功能
-
- 步驟1:調用方法傳入參數擷取傳回值
- 步驟2:設定分頁攔截器
- 步驟3:運作測試程式
- 3 DQL程式設計控制
-
- 3.1 條件查詢
-
- 3.1.1 條件查詢的類
- 3.1.2 環境建構
- 3.1.3 建構條件查詢
-
- QueryWrapper
- QueryWrapper的基礎上使用lambda
- LambdaQueryWrapper
- 3.1.4 多條件建構
- 3.1.5 null判定
-
- 方案一:添加屬性age2
- 方案二:建立一個模型類,讓其繼承User類,并在其中添加age2屬性
- MP給我們提供的簡化方式
- 3.2 查詢投影
-
- 3.2.1 查詢指定字段
- 3.2.2 聚合查詢
- 3.2.3 分組查詢
- 3.3 查詢條件
-
- 3.3.1 等值查詢
- 3.3.2 範圍查詢
- 3.3.3 模糊查詢
- 3.3.4 排序查詢
- 3.4 映射比對相容性
-
- 問題1:表字段與編碼屬性設計不同步
- 問題2:編碼中添加了資料庫中未定義的屬性
- 問題3:采用預設查詢開放了更多的字段檢視權限
- 知識點1:@TableField
- 問題4:表名與編碼開發設計不同步
- 知識點2:@TableName
- 代碼示範
-
- 步驟1:修改資料庫表user為tbl_user
- 步驟2:模型類添加@TableName注解
- 步驟3:将字段password修改成pwd
- 步驟4:使用@TableField映射關系
- 步驟5:添加一個資料庫表不存在的字段
- 步驟6:使用@TableField排除字段
- 步驟7:查詢時将pwd隐藏
- 4 DML程式設計控制
-
- 4.1 id生成政策控制
-
- 知識點1:@TableId
- 4.1.1 環境建構
- 4.1.2 代碼示範
-
- AUTO政策
-
- 步驟1:設定生成政策為AUTO
- 步驟2:删除測試資料并修改自增值
- 步驟3:運作新增方法
- INPUT政策
-
- 步驟1:設定生成政策為INPUT
- 步驟2:添加資料手動設定ID
- 步驟3:運作新增方法
- ASSIGN_ID政策
-
- 步驟1:設定生成政策為ASSIGN_ID
- 步驟2:添加資料不設定ID
- 步驟3:運作新增方法
- ASSIGN_UUID政策
-
- 步驟1:設定生成政策為ASSIGN_UUID
- 步驟2:修改表的主鍵類型
- 步驟3:添加資料不設定ID
- 步驟4:運作新增方法
- 4.1.3 ID生成政策對比
- 4.1.4 簡化配置
-
- 模型類主鍵政策設定
- 資料庫表與模型類的映射關系
- 4.2 多記錄操作
- 4.3 邏輯删除
-
- 步驟1:修改資料庫表添加`deleted`列
- 步驟2:實體類添加屬性
- 步驟3:運作删除方法
- 知識點1:@TableLogic
- 4.4 樂觀鎖
-
- 4.4.1 概念
- 4.4.2 實作思路
- 4.4.3 實作步驟
-
- 步驟1:資料庫表添加列
- 步驟2:在模型類中添加對應的屬性
- 步驟3:添加樂觀鎖的攔截器
- 步驟4:執行更新操作
- 5 快速開發
-
- 5.1 代碼生成器原理分析
- 5.2 代碼生成器實作
-
- 步驟1:建立一個Maven項目
- 代碼2:導入對應的jar包
- 步驟3:編寫引導類
- 步驟4:建立代碼生成類
- 步驟5:運作程式
- 5.3 MP中Service的CRUD
1 MyBatisPlus入門案例與簡介
1.1 入門案例
MybatisPlus(簡稱MP)是基于MyBatis架構基礎上開發的增強型工具,旨在簡化開發、提供效率。
開發方式
- 基于MyBatis使用MyBatisPlus
- 基于Spring使用MyBatisPlus
- 基于SpringBoot使用MyBatisPlus
回顧SpringBoot整合Mybatis的開發過程:
建立SpringBoot工程
勾選配置使用的技術,能夠實作自動添加起步依賴包
設定dataSource相關屬性(JDBC參數)
定義資料層接口映射配置
我們可以參考着上面的這個實作步驟把SpringBoot整合MyBatisPlus來快速實作下,具體的實作步驟為:
步驟1:建立資料庫及表
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user (
id bigint(20) primary key auto_increment,
name varchar(32) not null,
password varchar(32) not null,
age int(3) not null ,
tel varchar(32) not null
);
insert into user values(1,'Tom','tom',3,'18866668888');
insert into user values(2,'Jerry','jerry',4,'16688886666');
insert into user values(3,'Jock','123456',41,'18812345678');
insert into user values(4,'傳智播客','itcast',15,'4006184000');
步驟2:建立SpringBoot工程
步驟3:勾選配置使用技術
//由于MP并未被收錄到idea的系統内置配置,無法直接選擇加入,需要手動在pom.xml中配置添加
步驟4:pom.xml補全依賴
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
//druid資料源可以加也可以不加。這是因為SpringBoot有内置的資料源,可以配置成使用Druid資料源
//從MP的依賴關系可以看出,通過依賴傳遞已經将MyBatis與MyBatis整合Spring的jar包導入。是以我們不需要額外在添加MyBatis的相關jar包
步驟5:添加MP的相關配置資訊
resources預設生成的是properties配置檔案,可以将其替換成yml檔案,并在檔案中配置資料庫連接配接的相關資訊:
application.yml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC #要修改的位置
username: root
password: root
//serverTimezone是用來設定時區,UTC是标準時區,和咱們的時間差8小時,是以可以将其修改為
Asia/Shanghai
步驟6:根據資料庫表建立實體類
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
//setter...getter...toString方法略
}
步驟7:建立Dao接口
@Mapper
public interface UserDao extends BaseMapper<User>{
}
步驟8:編寫引導類
@SpringBootApplication
//@MapperScan("com.itheima.dao")
public class Mybatisplus01QuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus01QuickstartApplication.class, args);
}
}
//Dao接口要想被容器掃描到,有兩種解決方案:
- 方案一:在Dao接口上添加
注解,并且確定Dao處在引導類所在包或其子包中@Mapper
- 該方案的缺點是需要在每一Dao接口中添加注解
- 方案二:在引導類上添加
注解,其屬性為所要掃描的Dao所在包@MapperScan
- 該方案的好處是隻需要寫一次,則指定包下的所有Dao接口都能被掃描到,
就可以不寫。@Mapper
- 該方案的好處是隻需要寫一次,則指定包下的所有Dao接口都能被掃描到,
步驟9:編寫測試類
@SpringBootTest
class MpDemoApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testGetAll() {
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}
引出問題 userDao注入的時候下面有紅線提示的原因是什麼?
- UserDao是一個接口,不能執行個體化對象
- 隻有在伺服器啟動IOC容器初始化後,由架構建立DAO接口的代理對象來注入
- 現在伺服器并未啟動,是以代理對象也未建立,IDEA查找不到對應的對象注入,是以提示報紅
- 一旦服務啟動,就能注入其代理對象,是以該錯誤提示不影響正常運作。
檢視運作結果:
跟之前整合MyBatis相比,你會發現我們不需要在DAO接口中編寫方法和SQL語句了,隻需要繼承
BaseMapper
接口即可。整體來說簡化很多。
1.2 MybatisPlus簡介
MyBatisPlus(簡稱MP)是基于MyBatis架構基礎上開發的增強型工具,旨在簡化開發、提高效率
通過剛才的案例,相信大家能夠體會簡化開發和提高效率這兩個方面的優點。
MyBatisPlus的官網為:
https://mp.baomidou.com/
現在的頁面中,這一行已經被删除,現在再去通路
https://mybatis.plus
會發現通路不到,這個就有很多可能性供我們猜想了,是以大家使用baomidou的網址進行通路即可。
官方文檔中有一張很多小夥伴比較熟悉的圖檔:
從這張圖中我們可以看出MP旨在成為MyBatis的最好搭檔,而不是替換MyBatis,是以可以了解為MP是MyBatis的一套增強工具,它是在MyBatis的基礎上進行開發的,我們雖然使用MP但是底層依然是MyBatis的東西,也就是說我們也可以在MP中寫MyBatis的内容。
對于MP的學習,大家可以參考着官方文檔來進行學習,裡面都有詳細的代碼案例。
MP的特性:
- 無侵入:隻做增強不做改變,不會對現有工程産生影響
- 強大的 CRUD 操作:内置通用 Mapper,少量配置即可實作單表CRUD 操作
- 支援 Lambda:編寫查詢條件無需擔心字段寫錯
- 支援主鍵自動生成
- 内置分頁插件
- ……
2 标準資料層開發
在這一節中我們重點學習的是資料層标準的CRUD(增删改查)的實作與分頁功能。代碼比較多,我們一個個來學習。
2.1 标準CRUD的MP方法
對于标準的CRUD功能都有哪些以及MP都提供了哪些方法可以使用呢?
案例中的環境搭建
就是入門案例的内容
2.2 新增
在進行新增之前,我們可以分析下新增的方法
- T:泛型,新增用來儲存新增資料
- int:傳回值,新增成功後傳回1,沒有新增成功傳回的是0
在測試類中進行新增操作:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave() {
User user = new User();
user.setName("黑馬程式員");
user.setPassword("itheima");
user.setAge(12);
user.setTel("4006184000");
userDao.insert(user);
}
}
執行測試後,資料庫表中就會添加一條資料。
引出問題 但是資料中的主鍵ID,有點長,那這個主鍵ID是如何來的?我們更想要的是主鍵自增,應該是5才對
這個是我們後面要學習的主鍵ID生成政策,這塊的這個問題,我們暫時先放放。
2.3 删除
在進行删除之前,我們可以分析下删除的方法:
引出問題 參數類型(Serializable)為什麼是一個序列化類?
從這張圖可以看出,
- String和Number是Serializable的子類,
- Number又是Float,Double,Integer等類的父類,
- 能作為主鍵的資料類型都已經是Serializable的子類,
- MP使用Serializable作為參數類型,就好比我們可以用Object接收任何資料類型一樣。
- int:傳回值類型,資料删除成功傳回1,未删除資料傳回0。
在測試類中進行删除操作:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testDelete() {
userDao.deleteById(1401856123725713409L);
}
}
2.4 修改
在進行修改之前,我們可以分析下修改的方法:
- T:泛型,需要修改的資料内容,注意因為是根據ID進行修改,是以傳入的對象中需要有ID屬性值
- int:傳回值,修改成功後傳回1,未修改資料傳回0
在測試類中進行新增操作:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate() {
User user = new User();
user.setId(1L);
user.setName("Tom888");
user.setPassword("tom888");
userDao.updateById(user);
}
}
//修改的時候,隻修改實體對象中有值的字段。
2.5 根據ID查詢
在進行根據ID查詢之前,我們可以分析下根據ID查詢的方法:
- Serializable:參數類型,主鍵ID的值
- T:根據ID查詢隻會傳回一條資料
在測試類中進行新增操作:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetById() {
User user = userDao.selectById(2L);
System.out.println(user);
}
}
2.6 查詢所有
在進行查詢所有之前,我們可以分析下查詢所有的方法:
- Wrapper:用來建構條件查詢的條件,目前我們沒有可直接傳為Null
- List:因為查詢的是所有,是以傳回的資料是一個集合
在測試類中進行新增操作:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll() {
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}
//我們所調用的方法都是來自于DAO接口繼承的BaseMapper類中。裡面的方法有很多,我們後面會慢慢去學習裡面的内容。
2.7 Lombok
Lombok,一個Java類庫,提供了一組注解,簡化POJO實體類開發。
模型類的編寫都需要哪些内容:
- 私有屬性
- setter…getter…方法
- toString方法
- 構造函數
對于模型類的編寫有沒有什麼優化方法?
就是我們接下來要學習的Lombok。
使用步驟
步驟1:添加lombok依賴
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--<version>1.18.12</version>-->
</dependency>
//版本可以不用寫,因為SpringBoot中已經管理了lombok的版本。
步驟2:安裝Lombok的插件
新版本IDEA已經内置了該插件,如果删除setter和getter方法程式有報紅,則需要安裝插件
如果在IDEA中找不到lombok插件,可以通路如下網站 https://plugins.jetbrains.com/plugin/6317-lombok/versions
根據自己IDEA的版本下載下傳對應的lombok插件,下載下傳成功後,在IDEA中采用離線安裝的方式進行安裝。
步驟3:模型類上添加注解
Lombok常見的注解有:
- @Setter:為模型類的屬性提供setter方法
- @Getter:為模型類的屬性提供getter方法
- @ToString:為模型類的屬性提供toString方法
- @EqualsAndHashCode:為模型類的屬性提供equals和hashcode方法
- @Data:是個組合注解,包含上面的注解的功能
- @NoArgsConstructor:提供一個無參構造函數
- @AllArgsConstructor:提供一個包含所有參數的構造函數
//Lombok的注解還有很多,上面标紅的三個是比較常用的,其他的大家後期用到了,再去補充學習。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
我如果隻想要有name和password的構造函數,該如何編寫?
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
2.8 分頁功能
分頁查詢使用的方法是:
- IPage:用來建構分頁查詢條件
- Wrapper:用來建構條件查詢的條件,目前我們沒有可直接傳為Null
- IPage:傳回值,你會發現建構分頁條件和方法的傳回值都是IPage
//IPage是一個接口,我們需要找到它的實作類來建構它,具體的實作類,可以進入到IPage類中按ctrl+h,會找到其有一個實作類為
Page
。
步驟1:調用方法傳入參數擷取傳回值
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
//分頁查詢
@Test
void testSelectPage(){
//1 建立IPage分頁對象,設定分頁參數,1為目前頁碼,3為每頁顯示的記錄數
IPage<User> page=new Page<>(1,3);
//2 執行分頁查詢
userDao.selectPage(page,null);
//3 擷取分頁結果
System.out.println("目前頁碼值:"+page.getCurrent());
System.out.println("每頁顯示數:"+page.getSize());
System.out.println("一共多少頁:"+page.getPages());
System.out.println("一共多少條資料:"+page.getTotal());
System.out.println("資料:"+page.getRecords());
}
}
步驟2:設定分頁攔截器
這個攔截器MP已經為我們提供好了,我們隻需要将其配置成Spring管理的bean對象即可。
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1 建立MybatisPlusInterceptor攔截器對象
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
//2 添加分頁攔截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
上面的代碼記不住咋辦呢?
這些内容在MP的官方文檔中有詳細的說明,我們可以檢視官方文檔類配置
步驟3:運作測試程式
如果想檢視MP執行的SQL語句,可以修改application.yml配置檔案,
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #列印SQL日志到控制台
打開日志後,就可以在控制台列印出對應的SQL語句,開啟日志功能性能就會受到影響,調試完後記得關閉。
3 DQL程式設計控制
3.1 條件查詢
3.1.1 條件查詢的類
MyBatisPlus将書寫複雜的SQL查詢條件進行了封裝,使用程式設計的形式完成查詢條件的組合。
這個我們在前面都有見過,比如查詢所有和分頁查詢的時候,都有看到過一個
Wrapper
類,這個類就是用來建構查詢條件的,如下圖所示:
那麼條件查詢如何使用Wrapper來建構呢?
3.1.2 環境建構
1 建立一個SpringBoot項目
2 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.5.0</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>mybatisplus_02_dql</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
編寫UserDao接口
@Mapper
public interface UserDao extends BaseMapper<User> {
}
編寫模型類
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
編寫引導類
@SpringBootApplication
public class Mybatisplus02DqlApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus02DqlApplication.class, args);
}
}
編寫配置檔案
# dataSource
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: root
# mp日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
編寫測試類
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}
最終建立的項目結構為:
測試的時候,控制台列印的日志比較多,速度有點慢而且不利于檢視運作結果,是以接下來我們把這個日志處理下:
取消初始化spring日志列印,resources目錄下添加logback.xml,名稱固定,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>
//logback.xml的配置内容,不是我們學習的重點,如果有興趣可以自行百度查詢。
取消MybatisPlus啟動banner圖示
application.yml添加如下内容:
# mybatis-plus日志控制台輸出
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: off # 關閉mybatisplus啟動圖示
取消SpringBoot的log列印
application.yml添加如下内容:
spring:
main:
banner-mode: off # 關閉SpringBoot啟動圖示(banner)
//解決控制台列印日志過多的相關操作可以不用去做,一般會被用來友善我們檢視程式運作的結果。
3.1.3 建構條件查詢
在進行查詢的時候,我們的入口是在Wrapper這個類上,因為它是一個接口,是以我們需要去找它對應的實作類,關于實作類也有很多,說明我們有多種建構查詢條件對象的方式,
QueryWrapper
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper qw = new QueryWrapper();
qw.lt("age",18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
}
lt: 小于(<) ,最終的sql語句為
有個小問題就是在寫條件的時候,容易出錯
比如age寫錯,就會導緻查詢不成功
QueryWrapper的基礎上使用lambda
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.lambda().lt(User::getAge, 10);//添加條件
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
}
User::getAget,為lambda表達式中的,類名::方法名,最終的sql語句為:
注意:建構LambdaQueryWrapper的時候泛型不能省。
此時我們再次編寫條件的時候,就不會存在寫錯名稱的情況,但是qw後面多了一層lambda()調用
LambdaQueryWrapper
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
這種方式就解決了上一種方式所存在的問題。
3.1.4 多條件建構
需求:查詢資料庫表中,年齡在10歲到30歲之間的使用者資訊
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 30);
lqw.gt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
gt:大于(>),最終的SQL語句為
建構多條件的時候,可以支援鍊式程式設計
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 30).gt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
需求:查詢資料庫表中,年齡小于10或年齡大于30的資料
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
or()就相當于我們sql語句中的
or
關鍵字,不加預設是
and
,最終的sql語句為:
3.1.5 null判定
null判定的意義,先來看一張圖
- 我們在做條件查詢的時候,一般會有很多條件可以供使用者進行選擇查詢。
- 這些條件使用者可以選擇使用也可以選擇不使用,比如我要查詢價格在8000以上的手機
- 在輸入條件的時候,價格有一個區間範圍,按照需求隻需要在第一個價格輸入框中輸入8000
- 背景在做價格查詢的時候,一般會讓 price>值1 and price <值2
- 因為前端沒有輸入值2,是以如果不處理的話,就會出現 price>8000 and price < null問題
- 這個時候查詢的結果就會出問題,具體該如何解決?
需求:查詢資料庫表中,根據輸入年齡範圍來查詢符合條件的記錄
使用者在輸入值的時候,
如果隻輸入第一個框,說明要查詢大于該年齡的使用者
如果隻輸入第二個框,說明要查詢小于該年齡的使用者
如果兩個框都輸入了,說明要查詢年齡在兩個範圍之間的使用者
思考第一個問題:背景如果想接收前端的兩個資料,該如何接收?
我們可以使用兩個簡單資料類型,也可以使用一個模型類,但是User類中目前隻有一個age屬性,如:
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
使用一個age屬性,如何去接收頁面上的兩個值呢?這個時候我們有兩個解決方案
方案一:添加屬性age2
這種做法可以但是會影響到原模型類的屬性内容
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
private Integer age2;
}
方案二:建立一個模型類,讓其繼承User類,并在其中添加age2屬性
UserQuery在擁有User屬性後同時添加了age2屬性。
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
@Data
public class UserQuery extends User {
private Integer age2;
}
環境準備好後,我們來實作下剛才的需求:
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
//模拟頁面傳遞過來的查詢資料
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
if(null != uq.getAge2()){
lqw.lt(User::getAge, uq.getAge2());
}
if( null != uq.getAge()) {
lqw.gt(User::getAge, uq.getAge());
}
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
上面的寫法可以完成條件為非空的判斷,但是問題很明顯,如果條件多的話,每個條件都需要判斷,代碼量就比較大
MP給我們提供的簡化方式
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
//模拟頁面傳遞過來的查詢資料
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());
lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
lt()方法
condition為boolean類型,傳回true,則添加條件,傳回false則不添加條件
3.2 查詢投影
3.2.1 查詢指定字段
查詢投影即
不查詢所有字段,隻查詢出指定内容的資料。
具體如何來實作?
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.select(User::getId,User::getName,User::getAge);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
select(…)方法用來設定查詢的字段列,可以設定多個,最終的sql語句為:
如果使用的不是lambda,就需要手動指定字段
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("id","name","age","tel");
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
最終的sql語句為:SELECT id,name,age,tel FROM user
3.2.2 聚合查詢
需求:聚合函數查詢,完成count、max、min、avg、sum的使用
count:總記錄數
max:最大值
min:最小值
avg:平均值
sum:求和
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper<User> lqw = new QueryWrapper<User>();
//lqw.select("count(*) as count");
//SELECT count(*) as count FROM user
//lqw.select("max(age) as maxAge");
//SELECT max(age) as maxAge FROM user
//lqw.select("min(age) as minAge");
//SELECT min(age) as minAge FROM user
//lqw.select("sum(age) as sumAge");
//SELECT sum(age) as sumAge FROM user
lqw.select("avg(age) as avgAge");
//SELECT avg(age) as avgAge FROM user
List<Map<String, Object>> userList = userDao.selectMaps(lqw);
System.out.println(userList);
}
}
為了在做結果封裝的時候能夠更簡單,我們将上面的聚合函數都起了個名稱,方面後期來擷取這些資料
3.2.3 分組查詢
需求:分組查詢,完成 group by的查詢使用
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("count(*) as count,tel");
lqw.groupBy("tel");
List<Map<String, Object>> list = userDao.selectMaps(lqw);
System.out.println(list);
}
}
groupBy為分組,最終的sql語句為
SELECT count(*) as count,tel FROM user GROUP BY tel
聚合與分組查詢,無法使用lambda表達式來完成
MP隻是對MyBatis的增強,如果MP實作不了,我們可以直接在DAO接口中使用MyBatis的方式實作
3.3 查詢條件
前面我們隻使用了lt()和gt(),除了這兩個方法外,MP還封裝了很多條件對應的方法,這一節我們重點把MP提供的查詢條件方法進行學習下。
MP的查詢條件有很多:
- 範圍比對(> 、 = 、between)
- 模糊比對(like)
- 空判定(null)
- 包含性比對(in)
- 分組(group)
- 排序(order)
- ……
3.3.1 等值查詢
需求:根據使用者名和密碼查詢使用者資訊
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
User loginUser = userDao.selectOne(lqw);
System.out.println(loginUser);
}
}
eq(): 相當于
=
,對應的sql語句為
selectList:查詢結果為多個或者單個
selectOne:查詢結果為單個
3.3.2 範圍查詢
需求:對年齡進行範圍查詢,使用lt()、le()、gt()、ge()、between()進行範圍查詢
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.between(User::getAge, 10, 30);
//SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?)
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
- gt():大于(>)
- ge():大于等于(>=)
- lt():小于(<)
- lte():小于等于(<=)
- between():between ? and ?
3.3.3 模糊查詢
需求:查詢表中name屬性的值以 J
開頭的使用者資訊,使用like進行模糊查詢
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.likeLeft(User::getName, "J");
//SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
- like():前後加百分号,如 %J%
- likeLeft():前面加百分号,如 %J
- likeRight():後面加百分号,如 J%
3.3.4 排序查詢
需求:查詢所有資料,然後按照id降序
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>();
/**
* condition :條件,傳回boolean,
當condition為true,進行排序,如果為false,則不排序
* isAsc:是否為升序,true為升序,false為降序
* columns:需要操作的列
*/
lwq.orderBy(true,false, User::getId);
userDao.selectList(lw
}
}
除了上面示範的這種實作方式,還有很多其他的排序方法可以被調用,如圖:
- orderBy排序
- condition:條件,true則添加排序,false則不添加排序
- isAsc:是否為升序,true升序,false降序
- columns:排序字段,可以有多個
- orderByAsc/Desc(單個column):按照指定字段進行升序/降序
- orderByAsc/Desc(多個column):按照多個字段進行升序/降序
- orderByAsc/Desc
- condition:條件,true添加排序,false不添加排序
- 多個columns:按照多個字段進行排序
除了上面介紹的這幾種查詢條件建構方法以外還會有很多其他的方法,比如isNull,isNotNull,in,notIn等等方法可供選擇,具體參考官方文檔的條件構造器來學習使用,具體的網址為:
https://mp.baomidou.com/guide/wrapper.html#abstractwrapper
3.4 映射比對相容性
問題1:表字段與編碼屬性設計不同步
當表的列名和模型類的屬性名發生不一緻,就會導緻資料封裝不到模型對象,這個時候就需要其中一方做出修改,那如果前提是兩邊都不能改又該如何解決?
MP給我們提供了一個注解
@TableField
,使用該注解可以實作模型類屬性名和表的列名之間的映射關系
問題2:編碼中添加了資料庫中未定義的屬性
當模型類中多了一個資料庫表不存在的字段,就會導緻生成的sql語句中在select的時候查詢了資料庫不存在的字段,程式運作就會報錯,錯誤資訊為:
Unknown column ‘多出來的字段名稱’ in ‘field list’
具體的解決方案用到的還是
@TableField
注解,它有一個屬性叫
exist
,設定該字段是否在資料庫表中存在,如果設定為false則不存在,生成sql語句查詢的時候,就不會再查詢該字段了。
問題3:采用預設查詢開放了更多的字段檢視權限
查詢表中所有的列的資料,就可能把一些敏感資料查詢到傳回給前端,這個時候我們就需要限制哪些字段預設不要進行查詢。
解決方案是
@TableField
注解的一個屬性叫
select
,該屬性設定預設是否需要查詢該字段的值,true(預設值)表示預設查詢該字段,false表示預設不查詢該字段。
知識點1:@TableField
名稱 | @TableField |
---|---|
類型 | 屬性注解 |
位置 | 模型類屬性定義上方 |
作用 | 設定目前屬性對應的資料庫表中的字段關系 |
相關屬性 | value(預設):設定資料庫表字段名稱 exist:設定屬性在資料庫表字段中是否存在,預設為true,此屬性不能與value合并使用 select:設定屬性是否參與查詢,此屬性與select()映射配置不沖突 |
問題4:表名與編碼開發設計不同步
該問題主要是表的名稱和模型類的名稱不一緻,導緻查詢失敗,這個時候通常會報如下錯誤資訊:
Table ‘databaseName.tableNaem’ doesn’t exist,翻譯過來就是資料庫中的表不存在。
解決方案是使用MP提供的另外一個注解
@TableName
來設定表與模型類之間的對應關系。
知識點2:@TableName
名稱 | @TableName |
---|---|
類型 | 類注解 |
位置 | 模型類定義上方 |
作用 | 設定目前類對應于資料庫表關系 |
相關屬性 | value(預設):設定資料庫表名稱 |
代碼示範
接下來我們使用案例的方式把剛才的知識示範下:
步驟1:修改資料庫表user為tbl_user
直接查詢會報錯,原因是MP預設情況下會使用模型類的類名首字母小寫當表名使用。
步驟2:模型類添加@TableName注解
@Data
@TableName("tbl_user")
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
步驟3:将字段password修改成pwd
直接查詢會報錯,原因是MP預設情況下會使用模型類的屬性名當做表的列名使用
步驟4:使用@TableField映射關系
@Data
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField(value="pwd")
private String password;
private Integer age;
private String tel;
}
步驟5:添加一個資料庫表不存在的字段
@Data
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField(value="pwd")
private String password;
private Integer age;
private String tel;
private Integer online;
}
直接查詢會報錯,原因是MP預設情況下會查詢模型類的所有屬性對應的資料庫表的列,而online不存在
步驟6:使用@TableField排除字段
@Data
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField(value="pwd")
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
步驟7:查詢時将pwd隐藏
@Data
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
4 DML程式設計控制
查詢相關的操作我們已經介紹完了,緊接着我們需要對另外三個,增删改進行内容的講解。挨個來說明下,首先是新增(insert)中的内容。
4.1 id生成政策控制
前面我們在新增的時候留了一個問題,就是新增成功後,主鍵ID是一個很長串的内容,我們更想要的是按照資料庫表字段進行自增長,在解決這個問題之前,我們先來分析下ID該如何選擇:
- 不同的表應用不同的id生成政策
- 日志:自增(1,2,3,4,……)
- 購物訂單:特殊規則(FQ23948AK3843)
- 外賣單:關聯地區日期等資訊(10 04 20200314 34 91)
- 關系表:可省略id
- ……
不同的業務采用的ID生成方式應該是不一樣的,那麼在MP中都提供了哪些主鍵生成政策,以及我們該如何進行選擇?
在這裡我們又需要用到MP的一個注解叫
@TableId
知識點1:@TableId
名稱 | @TableId |
---|---|
類型 | 屬性注解 |
位置 | 模型類中用于表示主鍵的屬性定義上方 |
作用 | 設定目前類中主鍵屬性的生成政策 |
相關屬性 | value(預設):設定資料庫表主鍵名稱 type:設定主鍵屬性的生成政策,值查照IdType的枚舉值 |
4.1.1 環境建構
在建構條件查詢之前,我們先來準備下環境
1 建立一個SpringBoot項目
2 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.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>mybatisplus_03_dml</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
編寫UserDao接口
@Mapper
public interface UserDao extends BaseMapper<User> {
}
編寫模型類
@Data
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
編寫引導類
@SpringBootApplication
public class Mybatisplus03DqlApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus03DqlApplication.class, args);
}
}
編寫配置檔案
# dataSource
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: root
# mp日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
編寫測試類
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}
測試
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave(){
User user = new User();
user.setName("黑馬程式員");
user.setPassword("itheima");
user.setAge(12);
user.setTel("4006184000");
userDao.insert(user);
}
@Test
void testDelete(){
userDao.deleteById(1401856123925713409L)
}
@Test
void testUpdate(){
User user = new User();
user.setId(3L);
user.setName("Jock666");
user.setVersion(1);
userDao.updateById(user);
}
}
最終建立的項目結構為:
4.1.2 代碼示範
AUTO政策
步驟1:設定生成政策為AUTO
@Data
@TableName("tbl_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
步驟2:删除測試資料并修改自增值
删除測試資料
因為之前生成主鍵ID的值比較長,會把MySQL的自動增長的值變的很大,是以需要将其調整為目前最新的id值。
步驟3:運作新增方法
會發現,新增成功,并且主鍵id也是從5開始
經過這三步的示範,會發現
AUTO
的作用是使用資料庫ID自增,在使用該政策的時候一定要確定對應的資料庫表設定了ID主鍵自增,否則無效。
接下來,我們可以進入源碼檢視下ID的生成政策有哪些?
打開源碼後,你會發現并沒有看到中文注釋,這就需要我們點選右上角的
Download Sources
,會自動幫你把這個類的java檔案下載下傳下來,我們就能看到具體的注釋内容。因為這個技術是國人制作的,是以他代碼中的注釋還是比較容易看懂的。
當把源碼下載下傳完後,就可以看到如下内容:
從源碼中可以看到,除了AUTO這個政策以外,還有如下幾種生成政策:
- NONE: 不設定id生成政策
- INPUT:使用者手工輸入id
- ASSIGN_ID:雪花算法生成id(可相容數值型與字元串型)
- ASSIGN_UUID:以UUID生成算法作為id生成政策
- 其他的幾個政策均已過時,都将被ASSIGN_ID和ASSIGN_UUID代替掉。
拓展:
分布式ID是什麼?
- 當資料量足夠大的時候,一台資料庫伺服器存儲不下,這個時候就需要多台資料庫伺服器進行存儲
- 比如訂單表就有可能被存儲在不同的伺服器上
- 如果用資料庫表的自增主鍵,因為在兩台伺服器上是以會出現沖突
- 這個時候就需要一個全局唯一ID,這個ID就是分布式ID。
INPUT政策
步驟1:設定生成政策為INPUT
@Data
@TableName("tbl_user")
public class User {
@TableId(type = IdType.INPUT)
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
**注意:**這種ID生成政策,需要将表的自增政策删除掉
步驟2:添加資料手動設定ID
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave(){
User user = new User();
//設定主鍵ID的值
user.setId(666L);
user.setName("黑馬程式員");
user.setPassword("itheima");
user.setAge(12);
user.setTel("4006184000");
userDao.insert(user);
}
}
步驟3:運作新增方法
如果沒有設定主鍵ID的值,則會報錯,錯誤提示就是主鍵ID沒有給值:
如果設定了主鍵ID,則資料添加成功,如下:
ASSIGN_ID政策
步驟1:設定生成政策為ASSIGN_ID
@Data
@TableName("tbl_user")
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
步驟2:添加資料不設定ID
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave(){
User user = new User();
user.setName("黑馬程式員");
user.setPassword("itheima");
user.setAge(12);
user.setTel("4006184000");
userDao.insert(user);
}
}
**注意:**這種生成政策,不需要手動設定ID,如果手動設定ID,則會使用自己設定的值。
步驟3:運作新增方法
生成的ID就是一個Long類型的資料。
ASSIGN_UUID政策
步驟1:設定生成政策為ASSIGN_UUID
使用uuid需要注意的是,主鍵的類型不能是Long,而應該改成String類型
@Data
@TableName("tbl_user")
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
步驟2:修改表的主鍵類型
主鍵類型設定為varchar,長度要大于32,因為UUID生成的主鍵為32位,如果長度小的話就會導緻插入失敗。
步驟3:添加資料不設定ID
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave(){
User user = new User();
user.setName("黑馬程式員");
user.setPassword("itheima");
user.setAge(12);
user.setTel("4006184000");
userDao.insert(user);
}
}
步驟4:運作新增方法
接下來我們來聊一聊雪花算法:
雪花算法(SnowFlake),是Twitter官方給出的算法實作 是用Scala寫的。其生成的結果是一個64bit大小整數,它的結構如下圖:
- 1bit,不用,因為二進制中最高位是符号位,1表示負數,0表示正數。生成的id一般都是用整數,是以最高位固定為0。
- 41bit-時間戳,用來記錄時間戳,毫秒級
- 10bit-工作機器id,用來記錄工作機器id,其中高位5bit是資料中心ID其取值範圍0-31,低位5bit是工作節點ID其取值範圍0-31,兩個組合起來最多可以容納1024個節點
- 序列号占用12bit,每個節點每毫秒0開始不斷累加,最多可以累加到4095,一共可以産生4096個ID
4.1.3 ID生成政策對比
介紹了這些主鍵ID的生成政策,我們以後該用哪個呢?
- NONE: 不設定id生成政策,MP不自動生成,約等于INPUT,是以這兩種方式都需要使用者手動設定,但是手動設定第一個問題是容易出現相同的ID造成主鍵沖突,為了保證主鍵不沖突就需要做很多判定,實作起來比較複雜
- AUTO:資料庫ID自增,這種政策适合在資料庫伺服器隻有1台的情況下使用,不可作為分布式ID使用
- ASSIGN_UUID:可以在分布式的情況下使用,而且能夠保證唯一,但是生成的主鍵是32位的字元串,長度過長占用空間而且還不能排序,查詢性能也慢
- ASSIGN_ID:可以在分布式的情況下使用,生成的是Long類型的數字,可以排序性能也高,但是生成的政策和伺服器時間有關,如果修改了系統時間就有可能導緻出現重複主鍵
- 綜上所述,每一種主鍵政策都有自己的優缺點,根據自己項目業務的實際情況來選擇使用才是最明智的選擇。
4.1.4 簡化配置
模型類主鍵政策設定
引出問題 對于主鍵ID的政策已經介紹完,但是如果要在項目中的每一個模型類上都需要使用相同的生成政策,如:
确實是稍微有點繁瑣,我們能不能在某一處進行配置,就能讓所有的模型類都可以使用該主鍵ID政策呢?
我們隻需要在配置檔案中添加如下内容:
mybatis-plus:
global-config:
db-config:
id-type: assign_id
配置完成後,每個模型類的主鍵ID政策都将成為assign_id.
資料庫表與模型類的映射關系
引出問題 MP會預設将模型類的類名名首字母小寫作為表名使用,假如資料庫表的名稱都以
tbl_
開頭,那麼我們就需要将所有的模型類上添加
@TableName
,如:
簡化方式為在配置檔案中配置如下内容:
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
設定表的字首内容,這樣MP就會拿
tbl_
加上模型類的首字母小寫,就剛好組裝成資料庫的表名。
4.2 多記錄操作
先來看下問題:
之前添加了很多商品到購物車,過了幾天發現這些東西又不想要了,該怎麼辦呢?
很簡單删除掉,但是一個個删除的話還是比較慢和費事的,是以一般會給使用者一個批量操作,也就是前面有一個複選框,使用者一次可以勾選多個也可以進行全選,然後删一次就可以将購物車清空,這個就需要用到
批量删除
的操作了。
具體該如何實作多條删除,我們找找對應的API方法
翻譯方法的字面意思為:删除(根據ID 批量删除),參數是一個集合,可以存放多個id值。
需求:根據傳入的id集合将資料庫表中的資料删除掉。
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testDelete(){
//删除指定多條資料
List<Long> list = new ArrayList<>();
list.add(1402551342481838081L);
list.add(1402553134049501186L);
list.add(1402553619611430913L);
userDao.deleteBatchIds(list);
}
}
執行成功後,資料庫表中的資料就會按照指定的id進行删除。
除了按照id集合進行批量删除,也可以按照id集合進行批量查詢,還是先來看下API
方法名稱翻譯為:查詢(根據ID 批量查詢),參數是一個集合,可以存放多個id值。
需求:根據傳入的ID集合查詢使用者資訊
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetByIds(){
//查詢指定多條資料
List<Long> list = new ArrayList<>();
list.add(1L);
list.add(3L);
list.add(4L);
userDao.selectBatchIds(list);
}
}
查詢結果就會按照指定傳入的id值進行查詢
4.3 邏輯删除
先來分析下問題:
這是一個員工和其所簽的合同表,關系是一個員工可以簽多個合同,是一個一(員工)對多(合同)的表
員工ID為1的張業績,總共簽了三個合同,如果此時他離職了,我們需要将員工表中的資料進行删除,會執行delete操作
如果表在設計的時候有主外鍵關系,那麼同時也得将合同表中的前三條資料也删除掉
引出問題 後期要統計所簽合同的總金額,就會發現對不上,原因是已經将員工1簽的合同資訊删除掉了
引出問題 如果隻删除員工不删除合同表資料,那麼合同的員工編号對應的員工資訊不存在,那麼就會出現垃圾資料,就會出現無主合同,根本不知道有張業績這個人的存在
是以經過分析,我們不應該将表中的資料删除掉,而是需要進行保留,但是又得把離職的人和在職的人進行區分,這樣就解決了上述問題,如:
區分的方式,就是在員工表中添加一列資料
deleted
,如果為0說明在職員工,如果離職則将其改完1,(0和1所代表的含義是可以自定義的)
是以對于删除操作業務問題來說有:
- 實體删除:業務資料從資料庫中丢棄,執行的是delete操作
- 邏輯删除:為資料設定是否可用狀态字段,删除時設定狀态字段為不可用狀态,資料保留在資料庫中,執行的是update操作
MP中邏輯删除具體該如何實作?
步驟1:修改資料庫表添加 deleted
列
deleted
- 字段名可以任意,内容也可以自定義,比如 代表正常,
代表删除1
- 可以在添加列的同時設定其預設值為 正常。
步驟2:實體類添加屬性
(1)添加與資料庫表的列對應的一個屬性名,名稱可以任意,如果和資料表列名對不上,可以使用@TableField進行關系映射,如果一緻,則會自動對應。
(2)辨別新增的字段為邏輯删除字段,使用
@TableLogic
@Data
//@TableName("tbl_user") 可以不寫是因為配置了全局配置
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
@TableLogic(value="0",delval="1")
//value為正常資料的值,delval為删除資料的值
private Integer deleted;
}
步驟3:運作删除方法
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testDelete(){
userDao.deleteById(1L);
}
}
從測試結果來看,邏輯删除最後走的是update操作,會将指定的字段修改成删除狀态對應的值。
引出問題 邏輯删除,對查詢有沒有影響呢?
執行查詢操作
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testFind(){
System.out.println(userDao.selectList(null));
}
}
運作測試,會發現列印出來的sql語句中會多一個查詢條件,如:
可想而知,MP的邏輯删除會将所有的查詢都添加一個未被删除的條件,也就是已經被删除的資料是不應該被查詢出來的。
引出問題 如果還是想把已經删除的資料都查詢出來該如何實作呢?
@Mapper
public interface UserDao extends BaseMapper<User> {
//查詢所有資料包含已經被删除的資料
@Select("select * from tbl_user")
public List<User> selectAll();
}
如果每個表都要有邏輯删除,那麼就需要在每個模型類的屬性上添加
@TableLogic
注解,如何優化?
在配置檔案中添加全局配置,如下:
mybatis-plus:
global-config:
db-config:
# 邏輯删除字段名
logic-delete-field: deleted
# 邏輯删除字面值:未删除為0
logic-not-delete-value: 0
# 邏輯删除字面值:删除為1
logic-delete-value: 1
介紹完邏輯删除,邏輯删除的本質為:
邏輯删除的本質其實是修改操作。如果加了邏輯删除字段,查詢資料時也會自動帶上邏輯删除字段。
執行的SQL語句為:
UPDATE tbl_user SET deleted=1 where id = ? AND deleted=0
執行資料結果為:
知識點1:@TableLogic
名稱 | @TableLogic |
---|---|
類型 | 屬性注解 |
位置 | 模型類中用于表示删除字段的屬性定義上方 |
作用 | 辨別該字段為進行邏輯删除的字段 |
相關屬性 | value:邏輯未删除值 delval:邏輯删除值 |
4.4 樂觀鎖
4.4.1 概念
業務并發現象帶來的問題:秒殺
- 假如有100個商品或者票在出售,為了能保證每個商品或者票隻能被一個人購買,如何保證不會出現超買或者重複賣
- 對于這一類問題,其實有很多的解決方案可以使用
- 第一個最先想到的就是鎖,鎖在一台伺服器中是可以解決的,但是如果在多台伺服器下鎖就沒有辦法控制,比如12306有兩台伺服器在進行賣票,在兩台伺服器上都添加鎖的話,那也有可能會導緻在同一時刻有兩個線程在進行賣票,還是會出現并發問題
- 我們接下來介紹的這種方式是針對于小型企業的解決方案,因為資料庫本身的性能就是個瓶頸,如果對其并發量超過2000以上的就需要考慮其他的解決方案了。
簡單來說,樂觀鎖主要解決的問題是當要更新一條記錄的時候,希望這條記錄沒有被别人更新。
4.4.2 實作思路
樂觀鎖的實作方式:
- 資料庫表中添加version列,比如預設值給1
- 第一個線程要修改資料之前,取出記錄時,擷取目前資料庫中的version=1
- 第二個線程要修改資料之前,取出記錄時,擷取目前資料庫中的version=1
- 第一個線程執行更新時,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 第二個線程執行更新時,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 假如這兩個線程都來更新資料,第一個和第二個線程都可能先執行
- 假如第一個線程先執行更新,會把version改為2,
- 第二個線程再更新的時候,set version = 2 where version = 1,此時資料庫表的資料version已經為2,是以第二個線程會修改失敗
- 假如第二個線程先執行更新,會把version改為2,
- 第一個線程再更新的時候,set version = 2 where version = 1,此時資料庫表的資料version已經為2,是以第一個線程會修改失敗
- 不管誰先執行都會確定隻能有一個線程更新資料,這就是MP提供的樂觀鎖的實作原理分析。
上面所說的步驟具體該如何實作呢?
4.4.3 實作步驟
分析完步驟後,具體的實作步驟如下:
步驟1:資料庫表添加列
列名可以任意,比如使用
version
,給列設定預設值為
1
步驟2:在模型類中添加對應的屬性
根據添加的字段列名,在模型類中添加對應的屬性值
@Data
//@TableName("tbl_user") 可以不寫是因為配置了全局配置
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
private Integer deleted;
@Version
private Integer version;
}
步驟3:添加樂觀鎖的攔截器
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定義Mp攔截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加樂觀鎖攔截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
步驟4:執行更新操作
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
User user = new User();
user.setId(3L);
user.setName("Jock666");
userDao.updateById(user);
}
}
你會發現,這次修改并沒有更新version字段,原因是沒有攜帶version資料。
添加version資料
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
User user = new User();
user.setId(3L);
user.setName("Jock666");
user.setVersion(1);
userDao.updateById(user);
}
}
你會發現,我們傳遞的是1,MP會将1進行加1,然後,更新回到資料庫表中。
是以要想實作樂觀鎖,首先第一步應該是拿到表中的version,然後拿version當條件在将version加1更新回到資料庫表中,是以我們在查詢的時候,需要對其進行查詢
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
//1.先通過要修改的資料id将目前資料查詢出來
User user = userDao.selectById(3L);
//2.将要修改的屬性逐一設定進去
user.setName("Jock888");
userDao.updateById(user);
}
}
大概分析完樂觀鎖的實作步驟以後,我們來模拟一種加鎖的情況,看看能不能實作多個人修改同一個資料的時候,隻能有一個人修改成功。
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
//1.先通過要修改的資料id将目前資料查詢出來
User user = userDao.selectById(3L); //version=3
User user2 = userDao.selectById(3L); //version=3
user2.setName("Jock aaa");
userDao.updateById(user2); //version=>4
user.setName("Jock bbb");
userDao.updateById(user); //verion=3?條件還成立嗎?
}
}
運作程式,分析結果:
樂觀鎖就已經實作完成了,如果對于上面的這些步驟記不住咋辦呢?
參考官方文檔來實作:
https://mp.baomidou.com/guide/interceptor-optimistic-locker.html#optimisticlockerinnerinterceptor
5 快速開發
5.1 代碼生成器原理分析
造句:
我們可以往空白内容進行填詞造句,比如:
在比如:
觀察我們之前寫的代碼,會發現其中也會有很多重複内容,比如:
那我們就想,如果我想做一個Book子產品的開發,是不是隻需要将紅色部分的内容全部更換成
Book
即可,如:
是以我們會發現,做任何子產品的開發,對于這段代碼,基本上都是對紅色部分的調整,是以我們把去掉紅色内容的東西稱之為模闆,紅色部分稱之為參數,以後隻需要傳入不同的參數,就可以根據模闆建立出不同子產品的dao代碼。
除了Dao可以抽取子產品,其實我們常見的類都可以進行抽取,隻要他們有公共部分即可。再來看下模型類的模闆:
- ① 可以根據資料庫表的表名來填充
- ② 可以根據使用者的配置來生成ID生成政策
- ③到⑨可以根據資料庫表字段名稱來填充
是以隻要我們知道是對哪張表進行代碼生成,這些内容我們都可以進行填充。
分析完後,我們會發現,要想完成代碼自動生成,我們需要有以下内容:
- 模闆: MyBatisPlus提供,可以自己提供,但是麻煩,不建議
- 資料庫相關配置:讀取資料庫擷取表和字段資訊
- 開發者自定義配置:手工配置,比如ID生成政策
5.2 代碼生成器實作
步驟1:建立一個Maven項目
代碼2:導入對應的jar包
<?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.5.1</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>mybatisplus_04_generator</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--spring webmvc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatisplus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!--代碼生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!--velocity模闆引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
步驟3:編寫引導類
@SpringBootApplication
public class Mybatisplus04GeneratorApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus04GeneratorApplication.class, args);
}
}
步驟4:建立代碼生成類
public class CodeGenerator {
public static void main(String[] args) {
//1.擷取代碼生成器的對象
AutoGenerator autoGenerator = new AutoGenerator();
//設定資料庫相關配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("root");
autoGenerator.setDataSource(dataSource);
//設定全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java"); //設定代碼生成位置
globalConfig.setOpen(false); //設定生成完畢後是否打開生成代碼所在的目錄
globalConfig.setAuthor("黑馬程式員"); //設定作者
globalConfig.setFileOverride(true); //設定是否覆寫原始生成的檔案
globalConfig.setMapperName("%sDao"); //設定資料層接口名,%s為占位符,指代子產品名稱
globalConfig.setIdType(IdType.ASSIGN_ID); //設定Id生成政策
autoGenerator.setGlobalConfig(globalConfig);
//設定包名相關配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.aaa"); //設定生成的包名,與代碼所在位置不沖突,二者疊加組成完整路徑
packageInfo.setEntity("domain"); //設定實體類包名
packageInfo.setMapper("dao"); //設定資料層包名
autoGenerator.setPackageInfo(packageInfo);
//政策設定
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tbl_user"); //設定目前參與生成的表名,參數為可變參數
strategyConfig.setTablePrefix("tbl_"); //設定資料庫表的字首名稱,子產品名 = 資料庫表名 - 字首名 例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true); //設定是否啟用Rest風格
strategyConfig.setVersionFieldName("version"); //設定樂觀鎖字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //設定邏輯删除字段名
strategyConfig.setEntityLombokModel(true); //設定是否啟用lombok
autoGenerator.setStrategy(strategyConfig);
//2.執行生成操作
autoGenerator.execute();
}
}
對于代碼生成器中的代碼内容,我們可以直接從官方文檔中擷取代碼進行修改,
https://mp.baomidou.com/guide/generator.html
步驟5:運作程式
運作成功後,會在目前項目中生成很多代碼,代碼包含
controller
,
service
,
mapper
和
entity
至此代碼生成器就已經完成工作,我們能快速根據資料庫表來建立對應的類,簡化我們的代碼開發。
5.3 MP中Service的CRUD
回顧我們之前業務層代碼的編寫,編寫接口和對應的實作類:
public interface UserService{
}
@Service
public class UserServiceImpl implements UserService{
}
接口和實作類有了以後,需要在接口和實作類中聲明方法
public interface UserService{
public List<User> findAll();
}
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
public List<User> findAll(){
return userDao.selectList(null);
}
}
MP看到上面的代碼以後就說這些方法也是比較固定和通用的,那我來幫你抽取下
是以MP提供了一個Service接口和實作類,分别是:
IService
和
ServiceImpl
,後者是對前者的一個具體實作。
以後我們自己寫的Service就可以進行如下修改:
public interface UserService extends IService<User>{
}
@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService{
}
修改以後的好處是,MP已經幫我們把業務層的一些基礎的增删改查都已經實作了,可以直接進行使用。
編寫測試類進行測試
@SpringBootTest
class Mybatisplus04GeneratorApplicationTests {
private IUserService userService;
@Test
void testFindAll() {
List<User> list = userService.list();
System.out.println(list);
}
}
在MP封裝的Service層都有哪些方法可以用?
檢視官方文檔:
https://mp.baomidou.com/guide/crud-interface.html
,這些提供的方法大家可以參考官方文檔進行學習使用,方法的名稱可能有些變化,但是方法對應的參數和傳回值基本類似。