大家在日常開發中應該能發現,單表的CRUD功能代碼重復度很高,也沒有什么難度。而這部分代碼量往往比較大,開發起來比較費時。
因此,目前企業中都會使用一些組件來簡化或省略單表的CRUD開發工作。目前在國內使用較多的一個組件就是MybatisPlus.
當然,MybatisPlus不僅僅可以簡化單表操作,而且還對Mybatis的功能有很多的增強。可以讓我們的開發更加的簡單,高效。
通過今天的學習,我們要達成下面的目標:
-
能利用MybatisPlus實現基本的CRUD
-
會使用條件構建造器構建查詢和更新語句
-
會使用MybatisPlus中的常用注解
-
會使用MybatisPlus處理枚舉、JSON類型字段
-
會使用MybatisPlus實現分頁
1.快速入門
為了方便測試,我們先創建一個新的項目,并準備一些基礎數據。
1.1.環境準備
復制課前資料提供好的一個項目到你的工作空間(不要包含空格和特殊字符):
然后用你的IDEA工具打開,項目結構如下:
注意配置一下項目的JDK版本為JDK11。首先點擊項目結構設置:
在彈窗中配置JDK:
接下來,要導入兩張表,在課前資料中已經提供了SQL文件:
對應的數據庫表結構如下:
最后,在application.yaml
中修改jdbc參數為你自己的數據庫參數:
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: MySQL123
logging:level:com.itheima: debugpattern:dateformat: HH:mm:ss
1.2.快速開始
比如我們要實現User表的CRUD,只需要下面幾步:
-
引入MybatisPlus依賴
-
定義Mapper
1.2.1引入依賴
MybatisPlus提供了starter,實現了自動Mybatis以及MybatisPlus的自動裝配功能,坐標如下:
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version>
</dependency>
由于這個starter包含對mybatis的自動裝配,因此完全可以替換掉Mybatis的starter。 最終,項目的依賴如下:
<dependencies><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>
1.2.2.定義Mapper
為了簡化單表CRUD,MybatisPlus提供了一個基礎的BaseMapper
接口,其中已經實現了單表的CRUD:
因此我們自定義的Mapper只要實現了這個BaseMapper
,就無需自己實現單表CRUD了。 修改mp-demo中的com.itheima.mp.mapper
包下的UserMapper
接口,讓其繼承BaseMapper
:
代碼如下:
package com.itheima.mp.mapper;
?
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;
?
public interface UserMapper extends BaseMapper<User> {
}
1.2.3.測試
新建一個測試類,編寫幾個單元測試,測試基本的CRUD功能:
package com.itheima.mp.mapper;
?
import com.itheima.mp.domain.po.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
?
import java.time.LocalDateTime;
import java.util.List;
?
@SpringBootTest
class UserMapperTest {
?@Autowiredprivate UserMapper userMapper;
?@Testvoid testInsert() {User user = new User();user.setId(5L);user.setUsername("Lucy");user.setPassword("123");user.setPhone("18688990011");user.setBalance(200);user.setInfo("{\"age\": 24, \"intro\": \"英文老師\", \"gender\": \"female\"}");user.setCreateTime(LocalDateTime.now());user.setUpdateTime(LocalDateTime.now());userMapper.insert(user);}
?@Testvoid testSelectById() {User user = userMapper.selectById(5L);System.out.println("user = " + user);}
?@Testvoid testSelectByIds() {List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L, 5L));users.forEach(System.out::println);}
?@Testvoid testUpdateById() {User user = new User();user.setId(5L);user.setBalance(20000);userMapper.updateById(user);}
?@Testvoid testDelete() {userMapper.deleteById(5L);}
}
可以看到,在運行過程中打印出的SQL日志,非常標準:
11:05:01 INFO 15524 --- [ ? ? ? ? ? main] com.zaxxer.hikari.HikariDataSource ? ? ? : HikariPool-1 - Starting...
11:05:02 INFO 15524 --- [ ? ? ? ? ? main] com.zaxxer.hikari.HikariDataSource ? ? ? : HikariPool-1 - Start completed.
11:05:02 DEBUG 15524 --- [ ? ? ? ? ? main] c.i.mp.mapper.UserMapper.selectById ? ? ?: ==> Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM user WHERE id=?
11:05:02 DEBUG 15524 --- [ ? ? ? ? ? main] c.i.mp.mapper.UserMapper.selectById ? ? ?: ==> Parameters: 5(Long)
11:05:02 DEBUG 15524 --- [ ? ? ? ? ? main] c.i.mp.mapper.UserMapper.selectById ? ? ?: <== ? ? Total: 1
user = User(id=5, username=Lucy, password=123, phone=18688990011, info={"age": 21}, status=1, balance=20000, createTime=Fri Jun 30 11:02:30 CST 2023, updateTime=Fri Jun 30 11:02:30 CST 2023)
只需要繼承BaseMapper就能省去所有的單表CRUD,是不是非常簡單!
1.3.常見注解
在剛剛的入門案例中,我們僅僅引入了依賴,繼承了BaseMapper就能使用MybatisPlus,非常簡單。但是問題來了: MybatisPlus如何知道我們要查詢的是哪張表?表中有哪些字段呢?
大家回憶一下,UserMapper在繼承BaseMapper的時候指定了一個泛型:
泛型中的User就是與數據庫對應的PO.
MybatisPlus就是根據PO實體的信息來推斷出表的信息,從而生成SQL的。默認情況下:
-
MybatisPlus會把PO實體的類名駝峰轉下劃線作為表名
-
MybatisPlus會把PO實體的所有變量名駝峰轉下劃線作為表的字段名,并根據變量類型推斷字段類型
-
MybatisPlus會把名為id的字段作為主鍵
但很多情況下,默認的實現與實際場景不符,因此MybatisPlus提供了一些注解便于我們聲明表信息。
1.3.1.@TableName
說明:
-
描述:表名注解,標識實體類對應的表
-
使用位置:實體類
示例:
@TableName("user") public class User {private Long id;private String name; }
TableName注解除了指定表名以外,還可以指定很多其它屬性:
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 表名 |
schema | String | 否 | "" | schema |
keepGlobalPrefix | boolean | 否 | false | 是否保持使用全局的 tablePrefix 的值(當全局 tablePrefix 生效時) |
resultMap | String | 否 | "" | xml 中 resultMap 的 id(用于滿足特定類型的實體類對象綁定) |
autoResultMap | boolean | 否 | false | 是否自動構建 resultMap 并使用(如果設置 resultMap 則不會進行 resultMap 的自動構建與注入) |
excludeProperty | String[] | 否 | {} | 需要排除的屬性名 @since 3.3.1 |
1.3.2.@TableId
說明:
-
描述:主鍵注解,標識實體類中的主鍵字段
-
使用位置:實體類的主鍵字段
示例:
@TableName("user")
public class User {@TableIdprivate Long id;private String name;
}
TableId
注解支持兩個屬性:
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 表名 |
type | Enum | 否 | IdType.NONE | 指定主鍵類型 |
IdType
支持的類型有:
值 | 描述 |
---|---|
AUTO | 數據庫 ID 自增 |
NONE | 無狀態,該類型為未設置主鍵類型(注解里等于跟隨全局,全局里約等于 INPUT) |
INPUT | insert 前自行 set 主鍵值 |
ASSIGN_ID | 分配 ID(主鍵類型為 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默認實現類為DefaultIdentifierGenerator雪花算法) |
ASSIGN_UUID | 分配 UUID,主鍵類型為 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默認 default 方法) |
ID_WORKER | 分布式全局唯一 ID 長整型類型(please use ASSIGN_ID) |
UUID | 32 位 UUID 字符串(please use ASSIGN_UUID) |
ID_WORKER_STR | 分布式全局唯一 ID 字符串類型(please use ASSIGN_ID) |
這里比較常見的有三種:
-
AUTO
:利用數據庫的id自增長 -
INPUT
:手動生成id -
ASSIGN_ID
:雪花算法生成Long
類型的全局唯一id,這是默認的ID策略
1.3.3.@TableField
說明:
描述:普通字段注解
示例:
@TableName("user")
public class User {@TableIdprivate Long id;private String name;private Integer age;@TableField("isMarried")private Boolean isMarried;@TableField("'concat'")private String concat;@TableField(exist = false)private String address;
}
一般情況下我們并不需要給字段添加@TableField
注解,一些特殊情況除外:
-
成員變量名與數據庫字段名不一致
-
成員變量是以
isXXX
命名,按照JavaBean
的規范,MybatisPlus
識別字段時會把is
去除,這就導致與數據庫不符。 -
成員變量名與數據庫一致,但是與數據庫的關鍵字沖突。使用
@TableField
注解給字段名添加轉義字符:``。 -
成員變量不是數據庫字段
支持的其它屬性如下:
屬性 | 類型 | 必填 | 默認值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 數據庫字段名 |
exist | boolean | 否 | true | 是否為數據庫表字段 |
condition | String | 否 | "" | 字段 where 實體查詢比較條件,有值設置則按設置的值為準,沒有則為默認全局的 %s=#{%s},參考(opens new window) |
update | String | 否 | "" | 字段 update set 部分注入,例如:當在version字段上注解update="%s+1" 表示更新時會 set version=version+1 (該屬性優先級高于 el 屬性) |
insertStrategy | Enum | 否 | FieldStrategy.DEFAULT | 舉例:NOT_NULL insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>) |
updateStrategy | Enum | 否 | FieldStrategy.DEFAULT | 舉例:IGNORED update table_a set column=#{columnProperty} |
whereStrategy | Enum | 否 | FieldStrategy.DEFAULT | 舉例:NOT_EMPTY where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if> |
fill | Enum | 否 | FieldFill.DEFAULT | 字段自動填充策略 |
select | boolean | 否 | true | 是否進行 select 查詢 |
keepGlobalFormat | boolean | 否 | false | 是否保持使用全局的 format 進行處理 |
jdbcType | JdbcType | 否 | JdbcType.UNDEFINED | JDBC 類型 (該默認值不代表會按照該值生效) |
typeHandler | TypeHander | 否 | 類型處理器 (該默認值不代表會按照該值生效) | |
numericScale | String | 否 | "" | 指定小數點后保留的位數 |
1.4.常見配置
MybatisPlus也支持基于yaml文件的自定義配置,詳見官方文檔:
https://www.baomidou.com/pages/56bac0/#%E5%9F%BA%E6%9C%AC%E9%85%8D%E7%BD%AE
大多數的配置都有默認值,因此我們都無需配置。但還有一些是沒有默認值的,例如:
-
實體類的別名掃描包
-
全局id類型
mybatis-plus:type-aliases-package: com.itheima.mp.domain.poglobal-config:db-config:id-type: auto # 全局id類型為自增長
需要注意的是,MyBatisPlus也支持手寫SQL的,而mapper文件的讀取地址可以自己配置:
mybatis-plus:mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,當前這個是默認值。
可以看到默認值是classpath*:/mapper/**/*.xml
,也就是說我們只要把mapper.xml文件放置這個目錄下就一定會被加載。
例如,我們新建一個UserMapper.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">
<mapper namespace="com.itheima.mp.mapper.UserMapper">
?<select id="queryById" resultType="User">SELECT * FROM user WHERE id = #{id}</select>
</mapper>
然后在測試類UserMapperTest
中測試該方法:
@Test
void testQuery() {User user = userMapper.queryById(1L);System.out.println("user = " + user);
}