找往期文章包括但不限于本期文章中不懂的知識點:
個人主頁:我要學編程程(?_?)-CSDN博客
所屬專欄:JavaEE
目錄
前言?
Mybatis-plus 快速上手
Mybatis-plus 復雜操作
常用注解
@TableName?
@TableField
@TableId
打印日志
條件構造器
QueryWrapper
UpdateWrapper?
LambdaQueryWrapper、LambdaUpdateWrapper
自定義SQL?
前言?
初始JavaEE篇 —— Mybatis操作數據庫(上)-CSDN博客
初始JavaEE篇 —— Mybatis操作數據庫(下)-CSDN博客
通過前面的學習,我們已經知道了如何使用 Mybatis 來操作數據庫了,相較于 JDBC 來說,Mybatis 的使用更加方便和容易上手,但是與我們今天學習的 Mybatis-plus 來說,Mybatis 操作數據庫的方式還是比較繁瑣的,因為需要我們自己去寫SQL語句來實現具體的操作,而Mybatis-plus對于簡單的查詢,根本就不需要我們自己寫SQL語句了,甚至連XML文件都無需生成了。
Mybatis-plus 快速上手
1、創建項目工程:
File -> New -> Project:
然后選擇對應的項目配置:
接著,選擇Lombok、MySQL driver依賴即可:
2、在pom文件中,添加Mybatis-plus 所需的依賴,刷新Maven:
SpringBoot 2.x:
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.11</version>
</dependency>
SpringBoot 3.x:
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.11</version>
</dependency>
3、在yml文件中,添加MySQ數據源配置,以及
# 配置數據庫連接
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
4、編寫對應的SQL腳本:
-- 創建數據庫
DROP DATABASE IF EXISTS mybatis_test;CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;-- 使用數據數據
USE mybatis_test;-- 創建表[用戶表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`username` VARCHAR ( 127 ) NOT NULL,`password` VARCHAR ( 127 ) NOT NULL,`age` TINYINT ( 4 ) NOT NULL,`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默認',`phone` VARCHAR ( 15 ) DEFAULT NULL,`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-刪除',`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; -- 添加用戶信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
5、編寫實體類:
@Data
public class UserInfo {private Integer id;private String username;private String password;private Integer age;private Integer gender;private String phone;private Integer deleteFlag;private Date createTime;private Date updateTime;
}
6、編寫mapper接口:
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {}
注意:這里只需要繼承 BaseMapper<T> 這個接口即可。
7、編寫測試方法:
@SpringBootTest
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Testvoid testSelectById() {System.out.println(userInfoMapper.selectById(1));}
}
8、運行測試方法,觀察結果:
從最終結果來看,查詢數據成功了,而我們并沒有在mapper接口中去編寫任何方法以及去實現,但最終卻能成功執行,這就是 Mybatis-plus 的強大之處。
接下來,我們測試其他的方法是否可以成功執行。
插入:
刪除:
修改:
以上這些基本的增刪改查都是可以使用Mybatis-plus提供的方法。?
學會了如何簡單使用Mybatis-plus之后,我們接下來就需要學習更加復雜的操作。
Mybatis-plus 復雜操作
常用注解
@TableName?
首先,我們來想一個問題:Mybatis-plus 是如何知道查詢的是哪個表的呢?這就是通過前面繼承的BaseMapper<T> 中泛型來決定的。但問題是數據庫中的表是 user_info,而實體類卻是UserInfo,兩者對應的名稱是不同的,這怎么找呢?Mybatis-plus 默認通過 駝峰 轉 蛇形 的方式,將實體類轉換為蛇形字段,從而去對應的數據庫中查找,因此,UserInfo 轉換后的結果就是 user_info,在數據庫中成功找到該表,因此最終執行的操作可以對應上。但問題又來了:在實際開發中,并不是所有的程序員都會遵守 Mybatis-plus 的規定的,萬一有人將 UserInfo 寫成了 user_info 呢?該怎么辦?Mybatis-plus 給我們提供了注解 @TableName,讓我們手動指定數據庫的表名,繼而其會根據表名去對應的數據庫中查找,如果找到的話,就會執行相應的操作;反之,則會拋出異常。
使用 @TableName 之前:?
使用 @TableName 之后:
注意:在實際開發中,最好還是按照規范來,避免出現一些不可預測的問題。?
@TableField
既然表名會出現上述情況,同樣字段名稱也是會出現上述情況的。(Mybatis-plus 也是按照BaseMapper中的泛型實體類的字段名去數據庫對應的數據表中查詢對應的字段名稱)這里是使用 @TableField 注解來指定查找的字段名。
使用 @TableField 之前:
使用 @TableField 之后:
@TableId
當 數據庫中主鍵名稱 和 屬性名 對應不上,不能使用 @TableField,而應該使用 @TableId。
如果我們在快速上手時, 去仔細觀察 插入方法 對應在 數據庫中的結果的話,會有一個奇怪的現象:我們并未指定 id 的值,正常來說是在原始的 id 上進行自增的,而數據庫中對應的值,更像是一個自增的隨機數。
這需要使用 @TableId 中的 type 屬性來解決。我們可以 ctrl + 左鍵 點擊 @TableId,去觀察:
當我們修改?@TableId 的 type 的值為 Type.AUTO之后,再去插入數據,觀察數據庫:
注意:表中的數據是默認是按照 id升序 來排列的,而圖中最前面兩條數據的 id 是要比1小的,因此我們可以猜測出其是負數(可以去賦值id到畫圖板上看一下),如果有小伙伴遇到的數是正數的話,那么最終自增的值應該是證書的最大值+1,因為如果插入的數比數據庫中原始的數據還要小的話,那么一直自增下去,總有一天,會到達該值,這時就會有沖突,因此,自增的值是從最大值開始自增的。
打印日志
為了方便學習與觀察SQL語句的執行結果,我們可以配置SQL語句的執行日志。
# 配置 Mybatis-plus 日志
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
該日志和我們前面學習的Mybatis的日志只是最開始的前綴不一致,Mybatis 對應的前綴是 Mybatis,而 Mybatis-plus 對應的前綴是 Mybatis-plus。
條件構造器
前面學習了簡單的CRUD,但在實際開發中肯定不止這些場景,還有一些復雜查詢等操作,同樣Mybatis-plus 也是提供了相應的支持。
MyBatis-Plus 提供了一套強大的條件構造器(Wrapper),用于構建復雜的數據庫查詢條件。Wrapper 類允許開發者以鏈式調用的方式構造查詢條件,無需編寫繁瑣的 SQL 語句,從而提高開發效率并減少 SQL 注入的風險。
在 MyBatis-Plus 中,Wrapper 類是構建查詢和更新條件(這里指的是where子句)的核心工具。以下是主要的 Wrapper 類及其功能:
AbstractWrapper:這是一個抽象基類,提供了所有 Wrapper 類共有的方法和屬性。它定義了條件構造的基本邏輯,包括字段(column)、值(value)、操作符(condition)等。所有的 QueryWrapper、UpdateWrapper、LambdaQueryWrapper 和 LambdaUpdateWrapper 都繼承自 AbstractWrapper。
QueryWrapper:專門用于構造查詢條件,支持基本的等于、不等于、大于、小于等各種常見操作。它允許你以鏈式調用的方式添加多個查詢條件,并且可以組合使用?
and
?和?or
?邏輯。UpdateWrapper:用于構造更新條件,可以在更新數據時指定條件。與 QueryWrapper 類似,它也支持鏈式調用和邏輯組合。使用 UpdateWrapper 可以在不創建實體對象的情況下,直接設置更新字段和條件。
LambdaQueryWrapper:這是一個基于 Lambda 表達式的查詢條件構造器,它通過 Lambda 表達式來引用實體類的屬性,從而避免了硬編碼字段名。這種方式提高了代碼的可讀性和可維護性,尤其是在字段名可能發生變化的情況下。
LambdaUpdateWrapper:類似于 LambdaQueryWrapper,LambdaUpdateWrapper 是基于 Lambda 表達式的更新條件構造器。它允許你使用 Lambda 表達式來指定更新字段和條件,同樣避免了硬編碼字段名的問題。
接下來就學習具體如何使用:
QueryWrapper
QueryWrapper 并不只用于查詢語句,只要是涉及到了where子句的地方,都可以使用,即刪除、修改、查詢都能使用。
查詢:
SQL:?
select id, username, password, age from user_info
where age = 18 and username like '%admin%';
測試代碼:
@Test
void testSelect() {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();// 構造SQL語句queryWrapper.select("id", "username", "password", "age").eq("age", 18).like("username", "%admin%");// 執行SQL語句System.out.println(userInfoMapper.selectList(queryWrapper));
}
QueryWrapper的select方法需要傳輸兩個參數,一個是Boolean類型,當其為true時,才會將后面的字段列表加入最終的SQL中,eq方法是equals的簡寫,用于設置單個字段的相等條件,like方法用于構建模糊查詢條件。當SQL語句構建完成之后,最終需要執行,還得通過Mybatis-plus的提供的方法來調用。
注意:select方法、eq方法以及like方法傳輸的第一個參數都是對應數據庫的字段名,而不是實體類的屬性名。
更新:
SQL:
update user_info set delete_flag = 1 where age < 20;
測試代碼:
@Test
void testUpdate2() {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.lt("age", 20);UserInfo userInfo = new UserInfo();userInfo.setDeleteFlag(1);userInfoMapper.update(userInfo, queryWrapper);
}
這里的 lt 是 less than 的縮寫,用于設置單個字段的小于條件。le 是 less than or equals to 的縮寫,用于設置單個字段的小于等于條件。ge 是 greater than or equals to 的縮寫,用于設置單個字段的大于等于條件。gt 是 greater than 的縮寫,用于設置單個字段的大于條件。ne 是 not equals 的縮寫,用于設置單個字段的不相等條件。
刪除:
SQL:
delete from user_info where age = 18;
測試代碼:
@Test
void testDelete2() {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("age", 18);userInfoMapper.delete(queryWrapper);
}
UpdateWrapper?
對于更新操作,可以直接使用 UpdateWrapper,這樣不必創建實體類,從而直接設置更新字段和條件。
基礎更新:
SQL:
update user_info set delete_flag = 0, age = 5 where id in (1,2,3);
測試代碼:
@Test
void testUpdate3() {UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();updateWrapper.set("delete_flag", 0).set("age", 5).in("id", List.of(1, 2, 3));userInfoMapper.update(updateWrapper);
}
set?用于設置更新語句中的 SET 字段。通過調用?set
?方法,可以指定在更新操作中要修改的字段及其新值。in?用于設置單個字段的 IN 條件,即字段的值在給定的集合中。
基于SQL更新:
update user_info set age = age + 10 where id in (1,2,3);
?測試代碼:
@Test
void testUpdate4() {UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();updateWrapper.setSql("age = age + 10").in("id", List.of(1, 2, 3));userInfoMapper.update(updateWrapper);
}
setSql?用于設置更新語句中的 SET 部分 SQL 包含 數據庫字段。
注意:如果上述傳遞的列名錯誤的話,也會發生badsql異常,可以通過打印的SQL語句觀察。
LambdaQueryWrapper、LambdaUpdateWrapper
QueryWrapper和UpdateWrapper存在一個問題:需要寫死字段名,如果字段名發生變更,可能會
因為測試不到位釀成事故(因為字段名是String類型)。
LambdaQueryWrapper:這是一個基于 Lambda 表達式的查詢條件構造器,它通過 Lambda 表達式來引用實體類的屬性,從而避免了硬編碼字段名。這種方式提高了代碼的可讀性和可維護性,尤其是在字段名可能發生變化的情況下。
LambdaUpdateWrapper:類似于 LambdaQueryWrapper,LambdaUpdateWrapper 是基于 Lambda 表達式的更新條件構造器。它允許你使用 Lambda 表達式來指定更新字段和條件,同樣避免了硬編碼字段名的問題。
還是直接來看代碼:
@Test
void testSelect() {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();// 構造SQL語句queryWrapper.select("id", "username", "password", "age").eq("age", 18).like("username", "%admin%");// 執行SQL語句System.out.println(userInfoMapper.selectList(queryWrapper));
}
上面是使用QueryWrapper來編寫的查詢,而下面是使用LambdaQueryWrapper編寫的查詢:
@Test
void testSelect2() {LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.select(UserInfo::getId,UserInfo::getUsername,UserInfo::getPassword,UserInfo::getAge).eq(UserInfo::getAge, 18).like(UserInfo::getUsername, "%admin%");userInfoMapper.selectList(lambdaQueryWrapper);
}
整體上來看對于字段參數都是采用Lambda表達式的寫法來通過實體類獲得屬性,從而根據屬性來推出對應字段名。?同樣使用下面的方式來創建LambdaQueryWrapper:
@Test
void testSelect3() {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();// 通過調用 Lambda 方法成功獲取到了 LambdaQueryWrapper對象LambdaQueryWrapper < UserInfo > lambda = queryWrapper.lambda();lambda.select(UserInfo::getId,UserInfo::getUsername,UserInfo::getPassword,UserInfo::getAge).eq(UserInfo::getAge, 18).like(UserInfo::getUsername, "%admin%");userInfoMapper.selectList(queryWrapper);
}
同樣LambdaUpdateWrapper的寫法也是類似的。
@Test
void testUpdate5() {// 同樣可以直接new LambdaUpdateWrapper// 也可以調用UpdateWrapper的lambda方法UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();updateWrapper.lambda().set(UserInfo::getDeleteFlag, 0).set(UserInfo::getAge, 10).in(UserInfo::getId, Arrays.asList(1, 2, 3));userInfoMapper.update(updateWrapper);
}
自定義SQL?
在實際開發中,Mybatis-plus提供的操作可能并不滿足我們的實際需求,Mybatis-plus也提供了自定義SQL的功能,我們可以利用Wrapper構造查詢條件,再結合Mapper編寫SQL。
注意:Mybatis-plus支持自定義SQL的版本要 3.0.7 及其以上。
SQL:
select id, username, password, age from user_info where username = 'admin';
?Mapper:
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {@Select("select id, username, password, age " +"from user_info ${ew.customSqlSegment}")// wrapper 拼接的是 where子句List<UserInfo> selectUserInfo(@Param("ew") Wrapper<UserInfo> wrapper);
// List<UserInfo> selectUserInfo(@Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);
}
測試代碼:
@Test
void selectUserInfo() {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().eq(UserInfo::getUsername, "admin");System.out.println(userInfoMapper.selectUserInfo(queryWrapper));
}
注意:?
1、參數命名:在自定義SQL時,傳遞的Wrapper對象必須指定參數名為 "ew",且需要通過 @Param 注解來手動綁定參數名為 "ew",否則就會拋異常。
即使我們使用 Constants.WRAPPER 本質是還是綁定參數名為 "ew"?。這里的Constants要用maobidou包下的。
2、在SQL語句中,必須要使用 ${ew.customSqlSegment}來引用Wrapper對象生成的SQL片段。因為 Wrapper 對象生成的SQL片段存儲在 customSqlSegment 屬性中,因此引用它才能正確提取。
3、 雖然這里使用的是 ${},但是Mybatis-plus 對 Wrapper 條件 做了處理,并不會出現SQL注入的風險。
上述是使用注解的方式來自定義SQL,同樣我們也可以使用XML的方式來自定義SQL。?
在yml中配置XML文件的映射路徑:
# 配置XML文件的映射路徑
# 同樣只是前綴發生了變化,從Mybatis變為了Mybatis-plus
mybatis-plus:mapper-locations: classpath:mapper/*.xml
?Mapper:
List<UserInfo> selectUserInfo2(@Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);
XML:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace 表示的該xml文件實現的接口類的全限定名稱 -->
<mapper namespace="com.springboot.mybatisplusdemo.mapper.UserInfoMapper"><select id="selectUserInfo2" resultType="com.springboot.mybatisplusdemo.model.UserInfo"><!-- 同樣這里只能使用 ${ew.customSqlSegment} -->select * from user_info ${ew.customSqlSegment}</select>
</mapper>
測試代碼:
@Test
void selectUserInfo2() {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().eq(UserInfo::getUsername, "admin");System.out.println(userInfoMapper.selectUserInfo2(queryWrapper));
}
上述都是簡單查詢,下面來學習如何編寫基于SQL進行更新。
SQL:
update user_info set age = age + 10 where id in (1, 2, 3);
Mapper:
@Update("update user_info set age = #{age} + 10 ${ew.customSqlSegment}")
Integer update(Integer age, @Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);
測試代碼:
@Test
void update() {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().in(UserInfo::getId, List.of(1, 2, 3));System.out.println("受影響的行數: " + userInfoMapper.update(10, queryWrapper));
}
Wrapper 只是添加了where子句,至于其他部分還是和Mybatis一樣去傳參和接收參數。
好啦!本期?初始JavaEE篇 —— Mybatis-plus 操作數據庫 的學習之旅 就到此結束啦!我們下一期再一起學習吧!