MyBatis-Plus 學習筆記
1. 簡介
1.1 介紹
MyBatus-Plus(簡稱 MP)是一個 MyBatis 的增強工具,在 MyBatis 的基礎上只增強不做改變,為簡化開發,提高效率而生。
就像 魂斗羅 中的 1P、2P,基友搭配,效率翻倍。
github:https://github.com/baomidou/mybatis-plus
1.2 特性
- 無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑。
- 損耗小:啟動即會自動注入基本 CURD ,性能基本無損耗,直接面向對象編程。
- 強大的 CRUD 操作:內置通用 Mapper、通用 Service,僅僅通過少量配置即可實現表單大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求。
- 支持 Lambada 形式調用:通過 Lambda 表達式,方便的編寫各類查詢條件,無需再擔心字段寫錯
- 支持主鍵自動生成:支持多達4中主鍵策略(內含分布式唯一 ID 生成器 -Sequence),可自由配置,完美解決主鍵問題
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式調用,實體類只需集成 Model 類即可進行強大的 CRUD 操作
- 支持自定義全局通用操作:支持全局通用方法注入(Write once,user anywhere)
- 內置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、Model 、Service、Controller 層代碼,支持模板引擎,更有超多自定義配置等您來使用
- 分頁插件支持多數據庫:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多種數據庫
- 內置性能分析插件:可輸出 Sql 語句以及其執行事件,建議開發測試時啟用該功能,能快速揪出慢查詢
- 內置全局攔截插件:提供全表 delete、update 操作只能分析阻斷,也可以自定義攔截規則,預防誤操作
1.3 框架結構
2. 快速開始
環境:
JDK 1.8
Maven 3.6.1
mysql 5.7.27
MyBtisPlus 3.3.2 (MP 3.3.X 版本相比於以前的版本改動了不少,使用時請注意版本號)
idea 2019.3
-
創建一個 SpringBoot 工程項目
-
引入依賴
<dependencies> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.23</version> </dependency> <!-- mybatis plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- SpringBoot 相關依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <!-- SpringBoot --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.0.RELEASE</version> </parent> <build> <!-- 插件 --> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> <!-- 靜態資源解析 --> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
創建測試所需要的表,並插入測試數據
# 創建表 DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主鍵ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年齡', email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱', PRIMARY KEY (id) ); #插入數據 DELETE FROM user; INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com');
-
編寫實體類
這里使用了 Lombok
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
-
配置數據源
在 application.yml 中配置數據源,這里使用的是 Druid 數據源,也可以使用默認的數據源
spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 password: root username: root # 切換成 Druid 數據源 type: com.alibaba.druid.pool.DruidDataSource #Spring Boot 默認是不注入這些屬性值的,需要自己綁定 #druid 數據源專有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置監控統計攔截的filters,stat:監控統計、log4j:日志記錄、wall:防御sql注入 #如果允許時報錯 java.lang.ClassNotFoundException: org.apache.log4j.Priority #則導入 log4j 依賴即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
-
創建 mapper 接口
創建 mapper 接口,並繼承 BaseMapper 接口,規定泛型類型
@Repository // 將 mapper 注冊到 Spring 容器中 public interface UserMapper extends BaseMapper<User> { }
-
在主啟動類中掃描接口
在主啟動類添加 @MapperScan() 注解,來掃描我們剛剛創建的 mapper 接口
@MapperScan("com.xp.mapper") // 掃描 mapper 接口 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
-
測試
編寫測試類
@SpringBootTest public class ApplicationTest { @Autowired private UserMapper userMapper; @Test void test(){ // UserMapper 中的 selectList() 方法的參數為 MyBatisPlus 內置的的條件封裝器 wrapper,所以不填寫就是無任何條件 List<User> userList = userMapper.selectList(null); for (User user : userList) { System.out.println(user); } } }
測試結果如下:
-
配置日志
如果我們想要知道 sql 語句的執行情況,我們可以配置日志。想要開啟日志功能,只需配置如下配置即可:
其中 log-impl 的值可以是其他的日志,不一定是我下面的,也可以配置 log4j
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3. 注解
除了 MyBatis 原本的注解,MP 還有一些自己的注解
3.1 @TableName
- 描述:表名注解
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 表名 |
schema | String | 否 | "" | schema |
keepGlobalPrefix | boolean | 否 | false | 是否保持使用全局的 tablePrefix 的值(如果設置了全局 tablePrefix 且自行設置了 value 的值) |
resultMap | String | 否 | "" | xml 中 resultMap 的 id |
autoResultMap | boolean | 否 | false | 是否自動構建 resultMap 並使用(如果設置 resultMap 則不會進行 resultMap 的自動構建並注入) |
關於 ‘autoResultMap’ 的說明
mp 會自動構建一個
ResultMap
並注入到 mybatis 里(一般用不上)。下面講兩句:因為 mp 底層是 MyBatis ,所以一些 MyBatis 的嘗試你要知道,mp 只是幫你注入了常用 crud 到 MyBatis 里,注入之前可以說是動態的(根據你entity的字段以及注解變化而變化),但是注入之后是靜態的(等於你寫在xml的東西)而對於直接指定typeHandler
,MyBatis 只支持你寫在兩個地方:
- 定義在 resultMap 里,只作用於 select 查詢的返回結果封裝
- 定義在
insert
和update
sql 的{property}
里的property
后面(例:#{property,typehandler-xxx.xxx.xxx}
),只作用域設置值
。而除了這兩種直接指定typeHandler
,MyBatis 有一個全局的掃描你自己的typeHandler
包的配置,這是根據你的property
的類型去找typeHandler
並使用。
3.2 @Tableld
- 描述:主鍵注解
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
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 長整性類型(please use ASSIGN_ID ) |
|
32 位 UUID 字符串(please use ASSIGN_UUID ) |
|
分布式全局唯一 ID 字符串類型(please user ASSIGN_ID ) |
3.3@TableField
- 描述:字段注解(非主鍵)
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 數據庫字段名 |
el | String | 否 | "" | 映射原生 #{...} 邏輯,相當於寫在 xml 里的 #{...} |
exist | boolean | 否 | true | 是否位數據庫表字段 |
condition | String | 否 | "" | 字段 where 實體查詢比較條件,有值設置則按設置的值為准,沒有則為默認全局的 %s=#{%s} |
update | String | 否 | "" | 字段 update set 部分注入。例如:update ="%s+1":表示更新時會 set version = version +1(該屬性優先級高於 el 屬性) |
insertStrategy | Enum | N | DEFAULT | 舉例 NOT_NULL: insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>) |
updateStrategy | Enum | N | DEFAULT | 舉例:IGNORED: update table_a set column=#{columnProperty} |
whereStrategy | Enum | N | DEFAULT | 舉例:NOT_EMPTY: where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if> |
fill | Enum | 否 | FieldFill.DEAFULT | 字段自動填充策略 |
select | boolean | 否 | true | 是否進行 select 查詢 |
keepGloabalFormat | boolean | 否 | false | 是否保持使用全局的 format 進行處理 |
jdbcType | JdbcType | 否 | JdbcType.UNDEFINED | JDBC類型(該默認值不代表會按照該值生效) |
typeHandler | Class<? extend TypeHandler> | 否 | UnknownTypeHandler.class | 類型處理器(該默認值不代表會按照該值生效) |
numericScale | String | 否 | "" | 指定小數點后保留機位 |
關於 ‘jdbcType’ 和 ‘numerucScale’ 的說明:
numericScale
只生肖與 update 的 sql。jdbcType
和typeHandler
如果不配合@TableName#autoResultMap = true
一起使用,也只生效於 update 的 sql。對於typeHandler
如果你的字段類型和 set 進去的類型為equals
關系,則只需要讓你的typeHandler
讓 MyBatis 加載到即可,不需要使用注解
FieldStrategy
值 | 描述 |
---|---|
IGNORED | 忽略判斷 |
NOT_NULL | 非 NULL 判斷 |
NOT_EMPTY | 非空判斷(只對字符串類型字段。其他類型字段依然為非NULL判斷) |
DEFAULT | 追隨全局配置 |
FieldFill
值 | 描述 |
---|---|
DEFAULT | 默認不處理 |
INSERT | 插入時填充字段 |
UPDATE | 更新時填充字段 |
INSERT_UPDATE | 插入和更新時填充字段 |
3.4 @Version
- 描述:樂觀鎖注解。標記
@Version
在字段上
3.5 @EnumValue
- 描述:通過枚舉類注解(注解在枚舉類字段上)
3.6 @TableLogic
- 描述:表字段邏輯處理注解(邏輯刪除)
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 邏輯未刪除值 |
delval | String | 否 | "" | 邏輯刪除值 |
3.7 @SqlParser
- 描述:租戶注解,支持 method 上以及 mapper 接口上
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
filter | boolean | 否 | false | true:表示過濾 SQL 解析,即不會進入 ISqlParser 解析鏈,否則會緊解析鏈並追加例如 tenant_id 等條件 |
3.8 @KeySequence
- 描述:序列主鍵策略
oracle
- 屬性:value、resultMap
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 序列名 |
clazz | Class | 否 | Long.class | id的類型,可以指定 String.class ,這樣返回的 Sequence 值是字符串 “1” |
4. CRUD
4.1 增加
MP 中提供了插入的方法 insert()
@SpringBootTest
public class ApplicationTest {
@Autowired
private UserMapper userMapper;
@Test
void test(){
System.out.println(userMapper.insert(new User(null, "張三", 18, "xp@qq.com")));
}
}
查看我們控制台打印的日志
可以發現,這 id 是我們看不懂的東西,可不是我們真正想要的(主鍵自增)
那么這個id是怎么生成的呢?
在我們上面的注解中的 @Tableld 有寫道,默認是 IdType.NONE。它是自動生成全局唯一的id。
如果我們需要修改成為自增,只需在主鍵上加@Tableld設置 type 修改成 IdType.AUTO
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
// 設置type=IdType.AUTO,讓主鍵自增
@TableId(type=IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
需要注意的是,我們要先確定我們的表的主鍵是已經設置自增的,不然會報錯
4.2 刪除
1. 普通刪除
// 普通刪除
@Test
void delete(){
// 根據id刪除
userMapper.deleteById(7L);
// 批量刪除
userMapper.deleteBatchIds(Arrays.asList(4L,5L,6L));
// 通過 map 刪除
Map<String, Object> map = new HashMap<>();
map.put("name","李四");
userMapper.deleteByMap(map);
}
2. 邏輯刪除
首先,我們先在數據庫的表中增加 deleted 字段,默認值為0 .(實際開發中是不允許修改數據庫的,只是為了測試方便)
修改實體類,讓數據庫的表和實體類一一對應,並在 deleted 上添加 @TableLogic 注解(3.3.0版本后可以不需要增加這個注解,只需在配置文件中開啟即可)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type=IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
@TableLogic
private Integer deleted;
}
在 application.yml 中配置邏輯刪除
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局邏輯刪除的實體字段名(since 3.3.0,配置后可以不需要在實體類字段上增加注解)
logic-delete-value: 1 # 邏輯已刪除的值 (默認為1)
logic-not-delete-value: 0 # 邏輯未刪除的值 (默認為0)
測試
// 邏輯刪除
@Test
void deleteLogic(){
// 通過 id 進行邏輯刪除
userMapper.deleteById(2);
// 批量刪除
userMapper.deleteBatchIds(Arrays.asList(1,8,2));
// 通過 map 刪除
Map<String, Object> map = new HashMap<>();
map.put("name","李四");
userMapper.deleteByMap(map);
// 查詢用戶,查看查詢語句是否發生了變化
System.out.println(userMapper.selectById(2));
}
3. 說明
只對自動注入的 sql 起效
- 插入:不作限制
- 查找:追加 where 條件過濾掉已刪除數據,且使用 wrapper.entity 生成的 where 條件會忽略該字段
- 更新:追加 where 條件防止更新到已刪除數據,且使用 wrapper.entity 生成的 where 條件會忽略該字段
- 刪除:轉變為更新
例如:
- 刪除:
update user set deleted=1 where id=1 and deleted=0
- 查找:
select id,name,deleted from user where deleted=0
字段類型支持說明:
- 支持所有數據類型(推薦使用
Integer
,boolean
,LocalDateTime
) - 如果數據庫字段使用
dateTime
,邏輯未刪除值和已刪除值支持配置為字符串null
,另一個值支持配置為函數來獲取值如now()
附錄:
- 邏輯刪除是為了方便數據恢復和保護數據本身價值等等的一種方案,但實際就是刪除。
- 如果你需要頻繁查出來看就不應使用邏輯刪除,而是以一個狀態去表示
4. 常見問題
-
如何 insert?
- 字段在數據庫定義默認值(推薦)
- insert 前自己 set 值
- 使用自動填充功能
-
刪除接口自動填充功能失效
-
使用
update
方法並UpdateWrapper.set(column,value)
(推薦) -
使用
update
方法並UpdateWrapper.setSql("column=value")
-
使用 Sql 注入器 注入
com.baomidou.mybatisplus.extension.injector.methods.LogicDeleteByIdWithFill
(推薦)
-
4.3 修改
1. 根據 id 進行修改
@SpringBootTest
public class ApplicationTest {
@Autowired
private UserMapper userMapper;
@Test
void test(){
System.out.println(userMapper.updateById(new User(6L, "李四", 3, null)));
}
}
查看控制台日志輸出,我們可以發現 MP 的插入方法是根據條件使用動態 SQL 實現的,這樣就節省了我們寫動態 SQL 的時間
2. 條件構造器修改
MP 提供了修改操作的條件構造器 UpdateWrapper
@Test
void queryWrapper(){
// 使用 updateWrapper, sql中的 where 條件在 updateWrapper 中的 Entity 中設置,也即是.setEntity()這個方法設置查詢條件
// 這里的update方法的第一個參數是設置 set 的值,可以為空, updateWrapper 中也可以設置 set 的值
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
// 這里,setEntity(T entity) 是用來構建 where 后面的條件,若不設置則默認是更改所有的數據
updateWrapper.set("name","李四").setEntity(new User().setName("張三"));
userMapper.update(null,updateWrapper);
}
我們查看控制台日志,可以看到 sql 語句和這兩個參數的對應關系
4.4 查詢
1. 普通查詢
@Test
void select(){
// 根據id查詢用戶
User user = userMapper.selectById(1);
System.out.println(user);
// 批量查詢
List<User> userList = userMapper.selectBatchIds(Arrays.asList(1, 2, 4, 5));
userList.forEach(System.out::println);
// 條件查詢之一 map
Map<String, Object> map = new HashMap<>();
map.put("name","李四");
map.put("age","3");
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
2. 分頁查詢
要進行分頁查詢,我們需要先配置 MP 的分頁插件,並將 MP 的分頁插件注冊到 Spring 容器中
在我們之前寫的 MyBatisPlus 配置類 MyBatisPlusConfig 類中增加如下代碼:
MyBatisPlusConfig
@Bean
PaginationInterceptor paginationInterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 設置請求的頁面大於最大頁后操作, true調回到首頁,false 繼續請求 默認false
// paginationInterceptor.setOverflow(false);
// 設置最大單頁限制數量,默認 500 條,-1 不受限制
// paginationInterceptor.setLimit(500);
// 開啟 count 的 join 優化,只針對部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
測試
// 分頁查詢
@Test
void selectPage(){
// 普通分頁查詢,Page構造器的第一個參數表示第幾頁,第二個參數表示每頁最多多少條數據
Page<User> page = new Page<>(2,5);
// 進行分頁查詢
Page<User> userPage = userMapper.selectPage(page, null);
// 獲取分頁記錄
List<User> userList = userPage.getRecords();
userList.forEach(System.out::println);
// 獲取總記錄數
System.out.println("總頁數:"+page.getTotal());
// 獲取總頁數
long pages = page.getPages();
System.out.println("總記錄數:"+pages);
// 每頁展示最大記錄條數
System.out.println("每頁展示最大記錄調試:"+page.getSize());
}
3. 條件構造器查詢
MP 中提供了查詢的條件構造器 QueryWrapper
@Test
void queryWrapper(){
// 創建 QueryWrapper 對象
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 構造條件,wrapper 是鏈式編程,所以可以構造很多的條件.這里的查詢條件是 name=張三 且 age>3
wrapper.eq("name","張三").gt("age",3);
// 查詢結果
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
5. 自動填充
根據阿里巴巴開發手冊:所有的數據庫表都應該有 gmt_create、gmt_modified 兩個字段。且更新數據表記錄時,必須同時更新記錄對應的 gmt_modified 字段值為當前時間。
MP 已經幫我們做好了自動填充的操作。
5.1 修改表結構
在我們原本的 User 表中增加 gmt_create 和 gmt_modified 兩個字段
5.2 同步實體類
同步實體類,並在實體類上增加注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type=IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date modifiedTime;
}
上面的注解介紹中已經有寫道,@TableField 這個注解中 fill 屬性可以設置自動填充策略
FieldFill 中有4個自動填充策略
public enum FieldFill {
DEFAULT, // 默認不處理
INSERT, // 插入時自動填充填充
UPDATE, // 更新時自動填充
INSERT_UPDATE; // 插入或更新時自動填充
private FieldFill() {
}
}
5.3 編寫填充策略
我們自定義一個元數據處理器 MyMetaObjectHandler,實現 MetaObjectHandler 接口
@Slf4j // 使用日志
@Component // 將我們自定義的元數據處理器注冊到 Spring 容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert ");
// 設置插入的填充 參數分別時:第一個為原數據對象,第二個為填充的變量名(注:這里填的是實體類的變量名,不是字段名),第三個為填充的內容的Class對象,第四個是填充的內容
this.strictInsertFill(metaObject,"createTime",Date.class,new Date());
this.strictInsertFill(metaObject,"modifiedTime",Date.class,new Date());
// fillStrategy 這個也可以設置自動填充,但是有bug 需要升級到 3.3.1.8-SNAPSHOT版本后
// this.fillStrategy(metaObject,"modifiedTime",new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update ");
// 設置修改的填充
this.strictUpdateFill(metaObject,"modifiedTime",Date.class,new Date());
// fillStrategy 這個也可以設置自動填充,但是有bug 需要升級到 3.3.1.8-SNAPSHOT版本后
// this.fillStrategy(metaObject,"modifiedTime",Date.class,new Date());
}
}
5.4 測試
我們先執行增加操作,看下增加操作是否自動填充了
@SpringBootTest
public class ApplicationTest {
@Autowired
private UserMapper userMapper;
@Test
void test(){
System.out.println(userMapper.insert(new User(null, "張三", 18, "xp@qq.com",null,null)));
}
}
可以發現,我們自定義的元數據處理器已經生效了,並且在執行插入操作的時候,createTime 和 modifiedTime 兩個字段都自動填充了現在的時間
我們再執行修改操作
@SpringBootTest
public class ApplicationTest {
@Autowired
private UserMapper userMapper;
@Test
void test(){
System.out.println(userMapper.updateById(new User(3L, "李四", 3, null,null,null))));
}
}
可以發現我們自定義的元數據處理器已經生效,而且自動更新了 modifiedTime 字段的修改時間
5.5 注意事項
-
字段必須聲明
@TableField
注解,屬性fill
選擇對應策略,該聲明告知MyBatis-Plus
需要預留注入sql
字段 -
填充處理器
MyMetaObjectHandler
在 SpringBoot 中需要聲明@Component
或@Bean
注入 -
要想根據注解
FieldFill.xxx
和字段名
以及字段類型
來取分必須使用父類的strictInsertFill
或者strictUpdateFill
方法 -
不需要根據任何來取分可以使用父類的
fillStrategy
方法 -
使用
update(T entity, Wrapper<T> updateWrapper)
或update(Wrapper<T> updateWrapper)
時,想要自動填充,則 entity 不能為空,否則則無法自動填充(特別注意)。可以使用update(new Entity(),new UpdateWrapper())
或者update(new UpdateWrapper(new Entity()))
來自動填充 -
上面例子的第二個參數,是實體類中的屬性名而不是數據庫表中的字段名
6. 樂觀鎖
在面試過程中,我們經常會被問到樂觀鎖,悲觀鎖。
6.1 什么是樂觀鎖,什么是悲觀鎖
什么是悲觀鎖(Pessimistic Lock):
當要對數據庫中的一條數據進行修改的時候,為了避免同時被其他人修改,最好的辦法就是直接對該數據進行加鎖以防止並發。這種借助數據庫所機制,在修改數據之前先鎖定,再修改的方式被稱之為悲觀並發控制【又名“悲觀鎖”,Perssimistic ,Concurrency Control,縮寫“PCC"】
什么是樂觀鎖(Optimistic Locking):
樂觀鎖是相對悲觀鎖而言的,樂觀鎖假設數據一般情況下不會造成沖突,所以再數據進行提交更新的時候,才會正式對數據的沖突與否進行檢測,如果發現沖突了,則返回給用戶錯誤的信息,讓用戶決定如何去做。
6.2 樂觀鎖插件
MP 提供了樂觀鎖插件
意圖:當要更新一條記錄的時候,希望這條記錄沒有被別人更新
樂觀鎖的實現方式:
- 取出記錄時,獲取當前 version
- 更新時,帶上這個 version
- 執行更新時, set version = newVersion where version = oldVersion
- 如果 version 不對,就更更新失敗
比如: 更新時的 version=1
update user set name='xp',version = version +1 where id=1 and version=1
如果執行更新操作時,數據已經被修改,即 version 不再是原來的 version =1,則會更新失敗
6.2.1 插件配置
-
修改數據表
將我們之前的 User 表添加 version 字段,並設置默認為1
-
將樂觀鎖插件注冊到 Spring 容器中
創建一個 MyBatisPlusConfig 配置類
@Configuration public class MyBatisPlusConfig { @Bean OptimisticLockerInterceptor optimisticLockerInterceptor(){ return new OptimisticLockerInterceptor(); } }
-
注解字段(必須要)
修改實體類,給 version 增加注解
@Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type=IdType.AUTO) private Long id; private String name; private Integer age; private String email; @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date modifiedTime; @Version private Integer version; }
特別說明:
- 支持的數據類型只有:int,Integer,long,Long,Date,TimeStamp,LocalDateTime
- 整數類型下
newVersion = oldVersion + 1
newVersion
會回寫到entity
中- 僅支持
updateById(id)
於update(entity,wrapper)
方法 - 在
update(entity,wrapper)
方法下,wrapper
不能再復用!!
-
測試
先模擬樂觀鎖更新成功,單線程時
// 模擬樂觀鎖成功 @Test void test1(){ // 1.查詢用戶信息 User user = userMapper.selectById(2); System.out.println(user); // 2.修改用戶信息 user.setName("xp01"); user.setId(2L); user.setAge(3); // 3.執行更新操作 userMapper.updateById(user); }
運行后控制台輸出如下:
可以看到,我們原本的版本號是1,執行更新操作后,MP 幫我們將版本號進行 +1 的操作
模擬樂觀鎖失敗,多線程下
// 模擬樂觀鎖失敗,多線程下 @Test void test2(){ // 線程1 User user = userMapper.selectById(2); user.setName("xp111"); user.setId(2L); user.setAge(1); System.out.println(user); // 模擬另一個線程執行插隊操作 User user1 = userMapper.selectById(2); user1.setName("xp222"); user1.setId(2L); user1.setAge(2); System.out.println(user1); userMapper.updateById(user1); // 樂觀鎖失敗 userMapper.updateById(user); }
運行后控制台輸出如下:
我們可以發現,在線程2插隊執行更新操作后,線程1 執行更新操作時,MP幫我們自動加上了樂觀鎖,所以線程1 更新數據失敗了
7. 性能分析插件
我們平時的開發中,會遇到一些慢 SQL。
MP 提供了性能分析插件,如果 sql 執行時間超過這個時間就停止運行!
但在 MP 的 3.2 版本后移除了性能分析插件
這里就基於 p6spy 插件來學習,想要是要的話得降版本使用
-
引入依賴
<!-- p6spy 性能分析插件 --> <dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version> </dependency>
-
配置 p6spy
在 application.yml 配置中將數據源的 driver-class-name 改成 p6spy 提供的驅動
spring: datasource: driver-class-name: com.p6spy.engine.spy.P6SpyDriver url: jdbc:p6spy:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
在 resources 目錄下創建 spy.properties 文件,並配置如下內容:
#3.2.1以上使用 modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory #3.2.1以下使用或者不配置 #modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory # 自定義日志打印 logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger #日志輸出到控制台 appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger # 使用日志系統記錄 sql #appender=com.p6spy.engine.spy.appender.Slf4JLogger # 設置 p6spy driver 代理 deregisterdrivers=true # 取消JDBC URL前綴 useprefix=true # 配置記錄 Log 例外,可去掉的結果集有error,info,batch,debug,statement,commit,rollback,result,resultset. excludecategories=info,debug,result,commit,resultset # 日期格式 dateformat=yyyy-MM-dd HH:mm:ss # 實際驅動可多個 #driverlist=org.h2.Driver # 是否開啟慢SQL記錄 outagedetection=true # 慢SQL記錄標准 2 秒 outagedetectioninterval=2
注意:
- driver-class-name 為 p6spy提供的驅動類
- url 前綴為 jdbc:p6spy 跟着冒號為對應數據庫連接地址
- 打印出 sql 為null,在 excludecategories 增加 commit
- 批量操作不打印 sql,去除 excludecategories 中的 batch
- 批量操作打印重復的問題請使用 MybatisPlusLogFactory (3.2.1新增)
- 該插件有性能損耗,不建議生產環境使用
-
簡單使用
執行之前查詢語句,查看控制台日志輸出
控制台中輸出了sql語句以及執行時間,查找資料好像沒有找到慢 SQL 可以是小輸,所以這里無法演示慢SQL 執行時的控制台輸出。MP 以前自帶的性能分析插件慢SQL標准是可以精確到毫秒級別的
8. 條件構造器
8.1 說明
- 以下出現的第一個入參
boolean condition
表示該條件是否加入最后生成的 sql 中 - 以下代碼塊內的那個方法均為從上往下補全個別 boolean 類型的入參,默認為
true
- 以下出現的泛型
Param
均為wrapper
的子類實例(均具有AbstractWrapper
的所有方法) - 以下方法在入參中出現的
R
為泛型,在普通 wrapper 中式String
,在 LambdaWrapper 中是函數(例:Entity::getId
,Entity
為實體類,getId
為字段id
的 get(Method)) - 以下方法入參中
R column
均表示數據庫字段。當R
具體類型為String
時則為數據庫字段名(字段名是數據庫關鍵字的自己用轉義符包裹!)而不是具體實體類數據字段名!!!,另當R
具體類型為SFunction
時項目 runtime 不支持 eclipse 自家的編譯器!!! - 以下舉例為使用 普通 wrapper,入參為
map
和list
的均以json
形式表現! - 使用中如果入參
map
或者list
為空 ,則不會加入最后生成的 sql 中!!!
8.2 警告
不支持以及不贊成在 RPC 調用中把 Wrapper 進行傳輸
- wrapper 很重
- 傳輸 wrapper 可以類比為你的 controller 用 map 接受值(開發一時爽,維護火葬場)
- 正確的 RPC 調用模式時寫一個 DTO 進行傳輸,被調用方再根據 DTO 執行響應的操作
- 我們拒絕接收任何關於 RPC 傳輸 Wrapper 報錯相關的 issue 甚至 pr
8.3 AbstractWrapper
說明
QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapepr)的父類用於生成 sql 的 where 條件,entity 屬性也用於生成 sql 的 where 條件
注意:entity 生成的 where 條件與使用各個 api 生成的 where 條件沒有任何關聯行為
allEq
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
- 全部 eq(或個別isNull)
個別參數說明:
params
:key
為數據庫字段名,value
為字段值
null2IsNull
:為 true
則再 map
的 value
為 null
時調用 isNull 方法, 為 false
時則忽略value
為 null
的
- 例1:
allEq({id:1,name:"老王",age:null})
--->id = 1 and name = '老王' and age is null
- 例2:
allEq({id:1,name:"老王",age:null}, false)
--->id = 1 and name = '老王'
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
個別參數說明:
filter
:過濾函數,是否允許字段傳入比對條件中
params
與 null2IsNull
:同上
- 例1:
allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})
--->name = '老王' and age is null
- 例2:
allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)
--->name = '老王'
eq
eq(R column, Object val)
eq(boolean condition, R column, Object val)
- 等於 =
- 例:
eq("name","老王")
--->name='老王'
ne
ne(R column, Object val)
ne(boolean condition, R column, Object val)
- 不等於 <>
- 例:
ne("name", "老王")
--->name <> '老王'
gt
ge(R column, Object val)
ge(boolean condition, R column, Object val)
- 大於 >
- 例:
gt("age", 18)
--->age > 18
ge
ge(R column, Object val)
ge(boolean condition, R column, Object val)
- 大於等於 >=
- 例:
ge("age", 18)
--->age >= 18
lt
lt(R column, Object val)
lt(boolean condition, R column, Object val)
- 小於 <
- 例:
lt("age", 18)
--->age < 18
le
le(R column, Object val)
le(boolean condition, R column, Object val)
- 小於等於 <=
- 例:
le("age", 18)
--->age <= 18
between
between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
- BETWEEN 值1 AND 值2
- 例:
between("age", 18, 30)
--->age between 18 and 30
noBetween
notBetween(R column, Object val1, Object val2)
notBetween(boolean condition, R column, Object val1, Object val2)
- NOT BETWEEN 值1 AND 值2
- 例:
notBetween("age", 18, 30)
--->age not between 18 and 30
like
like(R column, Object val)
like(boolean condition, R column, Object val)
- LIKE '%值%'
- 例:
like("name", "王")
--->name like '%王%'
notLike
notLike(R column, Object val)
notLike(boolean condition, R column, Object val)
- NOT LIKE '%值%'
- 例:
notLike("name", "王")
--->name not like '%王%'
LikeLeft
likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)
- LIKE '%值'
- 例:
likeLeft("name", "王")
--->name like '%王'
likeRight
likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)
- LIKE '值%'
- 例:
likeRight("name", "王")
--->name like '王%'
isNull
isNull(R column)
isNull(boolean condition, R column)
- 字段 IS NULL
- 例:
isNull("name")
--->name is null
isNotNull
isNotNull(R column)
isNotNull(boolean condition, R column)
- 字段 IS NOT NULL
- 例:
isNotNull("name")
--->name is not null
in
in(R column, Collection<?> value)
in(boolean condition, R column, Collection<?> value)
- 字段 IN (value.get(0), value.get(1), ...)
- 例:
in("age",{1,2,3})
--->age in (1,2,3)
in(R column, Object... values)
in(boolean condition, R column, Object... values)
- 字段 IN (v0, v1, ...)
- 例:
in("age", 1, 2, 3)
--->age in (1,2,3)
notIn
notIn(R column, Collection<?> value)
notIn(boolean condition, R column, Collection<?> value)
- 字段 NOT IN (value.get(0), value.get(1), ...)
- 例:
notIn("age",{1,2,3})
--->age not in (1,2,3)
notIn(R column, Object... values)
notIn(boolean condition, R column, Object... values)
- 字段 NOT IN (v0, v1, ...)
- 例:
notIn("age", 1, 2, 3)
--->age not in (1,2,3)
inSql
inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)
- 字段 IN ( sql語句 )
- 例:
inSql("age", "1,2,3,4,5,6")
--->age in (1,2,3,4,5,6)
- 例:
inSql("id", "select id from table where id < 3")
--->id in (select id from table where id < 3)
notInSql
notInSql(R column, String inValue)
notInSql(boolean condition, R column, String inValue)
- 字段 NOT IN ( sql語句 )
- 例:
notInSql("age", "1,2,3,4,5,6")
--->age not in (1,2,3,4,5,6)
- 例:
notInSql("id", "select id from table where id < 3")
--->id not in (select id from table where id < 3)
groupBy
groupBy(R... columns)
groupBy(boolean condition, R... columns)
- 分組:GROUP BY 字段, ...
- 例:
groupBy("id", "name")
--->group by id,name
orderByAsc
orderByAsc(R... columns)
orderByAsc(boolean condition, R... columns)
- 排序:ORDER BY 字段, ... ASC
- 例:
orderByAsc("id", "name")
--->order by id ASC,name ASC
orderByDesc
orderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)
- 排序:ORDER BY 字段, ... DESC
- 例:
orderByDesc("id", "name")
--->order by id DESC,name DESC
orderBy
orderBy(boolean condition, boolean isAsc, R... columns)
- 排序:ORDER BY 字段, ...
- 例:
orderBy(true, true, "id", "name")
--->order by id ASC,name ASC
having
having(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)
- HAVING ( sql語句 )
- 例:
having("sum(age) > 10")
--->having sum(age) > 10
- 例:
having("sum(age) > {0}", 11)
--->having sum(age) > 11
func
func(Consumer<Children> consumer)
func(boolean condition, Consumer<Children> consumer)
- func 方法(主要方便在出現if...else下調用不同方法能不斷鏈)
- 例:
func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})
or
or()
or(boolean condition)
拼接 OR
注意事項:
主動調用 or
表示緊接着下一個方法不是用 and 連接!(不調用 or
則默認為使用 and
連接)
- 例:
eq("id",1).or().eq("name","老王")
--->id = 1 or name = '老王'
or(Consumer<Param> consumer)
or(boolean condition, Consumer<Param> consumer)
- OR 嵌套
- 例:
or(i -> i.eq("name", "李白").ne("status", "活着"))
--->or (name = '李白' and status <> '活着')
and
and(Consumer<Param> consumer)
and(boolean condition, Consumer<Param> consumer)
- AND 嵌套
- 例:
and(i -> i.eq("name", "李白").ne("status", "活着"))
--->and (name = '李白' and status <> '活着')
nest
nested(Consumer<Param> consumer)
nested(boolean condition, Consumer<Param> consumer)
- 正常嵌套 不帶 AND 或者 OR
- 例:
nested(i -> i.eq("name", "李白").ne("status", "活着"))
--->(name = '李白' and status <> '活着')
apply
apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)
-
拼接 sql
注意事項:
該方法可用於數據庫函數動態入參得
params
對應得前面applySql
內部得(index
)部分。這樣是不會有 sql 注入風險的,反之會有! -
例:
apply("id = 1")
--->id = 1
-
例:
apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
-
例:
apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")
--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
last
last(String lastSql)
last(boolean condition, String lastSql)
-
無視優化規則直接拼接到 sql 的最后
注意事項:
只能調用一次,多次調用以最后一次為准,有 sql 注入的風險,請謹慎使用
-
例:
last("limit 1")
exists
exists(String existsSql)
exists(boolean condition, String existsSql)
- 拼接 EXISTS ( sql語句 )
- 例:
exists("select id from table where age = 1")
--->exists (select id from table where age = 1)
notExists
notExists(String notExistsSql)
notExists(boolean condition, String notExistsSql)
- 拼接 NOT EXISTS ( sql語句 )
- 例:
notExists("select id from table where age = 1")
--->not exists (select id from table where age = 1)
QueryWrapper
說明:
繼承自 AbstractWrapper,自身的內部屬性 entity 也用於生成 where 條件及 LambdaQueryWrapper ,可以通過 new QueryWrapper().lambda() 方法獲取
select
select(String... sqlSelect)
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
設置查詢字段
說明:
以上方法為兩類
第二類方法為過濾器查詢字段(主鍵除外),入參不包含 class 的調用前需要
wrapper
內的entity
屬性有值!這兩類方法重復調用以最后一次為准
- 例:
select("id", "name", "age")
- 例:
select(i -> i.getProperty().startsWith("test"))
UpdateWrapper
說明:
繼承自
AbstractWrapper
,自身的內部屬性entity
也用於生成 where 條件及LambdaUpdateWrapper
,可以通過new UpdateWrapper().lambda()
方法獲取!
set
set(String column, Object val)
set(boolean condition, String column, Object val)
- SQL SET 字段
- 例:
set("name", "老李頭")
- 例:
set("name", "")
--->數據庫字段值變為空字符串 - 例:
set("name", null)
--->數據庫字段值變為null
setSql
setSql(String sql)
- 設置 SET 部分 SQL
- 例:
setSql("name = '老李頭'")
lambda
獲取 LambdaWrapper
在QueryWrapper
中是獲取LambdaQueryWrapper
在UpdateWrapper
中是獲取LambdaUpdateWrapper
使用 Wrapper 自定義 SQL
需求來源:
再使用了
mybatis-plus
之后,自定義 SQL 的同時也想使用wrapper
的便利應該怎么辦?在mybatis-plus
版本3.0.7
得到了完美解決。版本需要大於或等於3.0.7
,以下兩種方案取其一即可
Service.java
mysqlMapper.getAll(Wrappers.<MysqlData>lambdaQuery().eq(MysqlData::getGroup, 1));
Mapper.xml
<select id="getAll" resultType="MysqlData">
SELECT * FROM mysql_data ${ew.customSqlSegment}
</select>
簡單示例
條件構造器的例子上面 CRUD 中有部分了,下面再寫幾個例子來找點感覺
@SpringBootTest
public class WrapperTest {
@Autowired
UserMapper userMapper;
// 查詢 name 不為空的,並且郵箱不為空,年齡大於12的用戶
@Test
void test1(){
// 創建 QueryWrapper
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 構建條件,wrapper是鏈式編程,所以可以一直 .下去
// isNotNull 有兩個重載方法,下面調用的這個方法的第一個參數表示的是數據庫表中的字段名
// gt 用來拼接 > 條件,第一個參數為數據庫表中的字段名,第二個是 > 號后的值
wrapper
.isNotNull("name")
.isNotNull("email")
.gt("age",3);
// 執行查詢操作,輸出查詢結果
userMapper.selectList(wrapper).forEach(System.out::println);
}
// 查詢名字等於李四的,且年齡小於30的用戶
@Test
void test2(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
// eq 拼接 = 條件,第一個參數為數據庫表中的字段名,第二個是 = 后的值
// lt 拼接 < 條件,第一個參數為數據庫表中的字段名,第二個是 《 后的值
wrapper
.eq("name","李四")
.lt("age",30);
// selectOne 查詢一個結果,如果返回多個結果,使用 selectMaps 或 selectList
System.out.println(userMapper.selectOne(wrapper));
}
// 查詢年齡再 20-30 歲之間的用戶
@Test
void test3(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
// between 拼接 between and 條件,第一個參數表示數據庫中的字段名,第二個參數為最小值,第三個參數為最大值
wrapper.between("age",20,30);
userMapper.selectList(wrapper).forEach(System.out::println);
}
// 模糊查詢
// 名字當中不包含 張 的,且是 李 開頭的用戶
@Test
void test4(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
// notLike 拼接 not like 條件,第一個參數為數據庫表中的字段名,第二個是 not like 后的值,它會自動拼接 % % 比如這里是 %張%
// likeRight ,拼接 like 條件,第一個參數為數據庫表中的字段名,第二個是 like后的值,它會再第二個參數后拼接 % 比如這里是 李%
wrapper
.notLike("name","張")
.likeRight("name","李");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
// 查詢用戶 id 小於2 的用戶
@Test
void test5(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
// inSql 在 in 條件后拼接 sql 語句,比如下面的這個執行后的語句為
// SELECT id,name,age,email,create_time,modified_time,version,deleted FROM user WHERE deleted=0 AND (id IN (select id from user where id < 2))
wrapper.inSql("id","select id from user where id < 2");
userMapper.selectList(wrapper).forEach(System.out::println);
}
// 通過 id 進行排序
@Test
void test6(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
// orderByDesc sql 在末尾添加降序排序,參數是可變參數,參數都是數據庫中字段名
wrapper.orderByDesc("id");
userMapper.selectList(wrapper).forEach(System.out::println);
}
}
Wrapper 十分重要,我們需要執行復雜 sql 語句時需要使用到,可以多加練習,找到規律來使用 Wrapper ,這樣會讓你的開發更加快速高效
9. 代碼生成器
AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個模塊的代碼,極大的提升了開發效率。
也就是說,MP 的代碼生成器可以節省我們寫很多重復繁瑣的代碼,快速搭建項目。
-
創建 user 表,或者使用我們剛剛的 user 表
如果是用剛剛的 user 表,最好加上注釋,這樣我們等下自動生成 Swagger 注解時就可以看到很明顯的效果
DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID', `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名', `age` int(11) NULL DEFAULT NULL COMMENT '年齡', `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '郵箱', `modified_time` datetime(6) NULL DEFAULT NULL COMMENT '修改時間', `create_time` datetime(6) NULL DEFAULT NULL COMMENT '創建時間', `version` int(1) NULL DEFAULT 1 COMMENT '版本號(樂觀鎖)', `deleted` int(1) NULL DEFAULT 0, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; INSERT INTO `user` VALUES (1, '張三', 3, 'test1@baomidou.com', '2020-07-31 14:10:17.902000', '2020-07-31 14:01:47.161240', 1, 0); INSERT INTO `user` VALUES (2, '李四', 18, 'test2@baomidou.com', '2020-07-31 14:10:17.902000', '2020-07-31 14:01:47.161240', 4, 0); INSERT INTO `user` VALUES (8, '王五', 18, 'xp@qq.com', '2020-07-31 14:10:17.902000', '2020-07-31 14:01:47.161240', 1, 0);
-
創建一個新的 SpringBoot 項目
-
引入依賴
引入的核心依賴是
mybatis-plus-boot-starter
、mysql-connector-java
、mybatis-plus-generator
、velocity-engine-core
。只要存在這四個依賴,則可以使用代碼生成器。數據源可以使用 Spring默認的,這里使用 Druid。數據庫連接驅動也不一定是要MySQL的。<dependencies> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.23</version> </dependency> <!-- mybatis plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- mybatis-plus-generator --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.3.2</version> </dependency> <!-- swagger-ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <!-- swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- velocity 模板引擎 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.2</version> </dependency> <!-- SpringBoot 相關依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
-
配置 application.xml
配置數據源
spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 password: root username: root # 切換成 Druid 數據源 type: com.alibaba.druid.pool.DruidDataSource #Spring Boot 默認是不注入這些屬性值的,需要自己綁定 #druid 數據源專有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置監控統計攔截的filters,stat:監控統計、log4j:日志記錄、wall:防御sql注入 #如果允許時報錯 java.lang.ClassNotFoundException: org.apache.log4j.Priority #則導入 log4j 依賴即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
-
編寫代碼生成器
根據步驟,先創建代碼生成器對象。然后 全局配置 => 配置數據源 => 包的配置 => 策略配置
@Test void generator(){ // 構建一個代碼生成器對象 AutoGenerator autoGenerator = new AutoGenerator(); // 1.全局配置 GlobalConfig globalConfig = new GlobalConfig(); globalConfig .setOutputDir(System.getProperty("user.dir") + "/src/main/java") // 代碼自動生成后存在的相對項目的位置 .setAuthor("xp") //設置作者名 .setOpen(false) .setFileOverride(false) // 是否覆蓋 .setServiceName("%sService") // 自定義Service類名,默認生成的 Service類名前面有 I 開頭 .setMapperName("%sMapper") .setServiceImplName("%sServiceImpl") .setControllerName("%sController") .setIdType(IdType.AUTO) // 配置主鍵策略,IdType.AUTO 代表主鍵自增 .setSwagger2(true); // 開啟 Swagger2 注解 autoGenerator.setGlobalConfig(globalConfig); // 將我們的全局設置注入到代碼生成器中 // 2. 配置數據源 DataSourceConfig dataSourceConfig = new DataSourceConfig(); dataSourceConfig .setUrl("jdbc:mysql://localhost:3306/mybatisplus?useUnicode=true&useSSL=false&characterEncoding=utf8") .setDriverName("com.mysql.jdbc.Driver") .setUsername("root") .setPassword("root") .setDbType(DbType.MYSQL); // 設置數據庫類型為mysql autoGenerator.setDataSource(dataSourceConfig); // 將我們的數據源注入到代碼生成器中 // 3. 包的配置 PackageConfig packageConfig = new PackageConfig(); packageConfig // .setModuleName("test") // 設置模塊名 .setController("controller") // 設置 controller 的包 .setEntity("model.entity") // 設置 實體類的包 .setService("service") // 設置 Service 接口的包 .setServiceImpl("service.impl") // 設置 Service 接口實現類的包 .setMapper("mapper") // 設置 mapper 的包 .setParent("com.xp"); // 設置這些包名的父包名 autoGenerator.setPackageInfo(packageConfig); // 將我們包的配置注入到代碼生成器中 // 4. 策略配置 StrategyConfig strategyConfig = new StrategyConfig(); // 自動填充配置 TableFill createTime = new TableFill("create_time", FieldFill.INSERT); TableFill modifiedTime = new TableFill("modified_time", FieldFill.INSERT_UPDATE); strategyConfig .setInclude("user") // 設置要映射的表名 .setNaming(NamingStrategy.underline_to_camel) // 駝峰命名 .setColumnNaming(NamingStrategy.underline_to_camel) // 字段名駝峰命名 .setLogicDeleteFieldName("deleted") // 自定義邏輯刪除字段 .setEntityLombokModel(true) .setEntityTableFieldAnnotationEnable(true) // 實體類字段生成注釋 .setVersionFieldName("version") // 樂觀鎖配置 .setTableFillList(Arrays.asList(createTime,modifiedTime)) .setControllerMappingHyphenStyle(true) // urlMapping駝峰轉字符 .setRestControllerStyle(true); // 生成的 Controller 加上 @RestController 注解 autoGenerator.setStrategy(strategyConfig); // 將我們的策略配置注入到代碼生成器中 // 5. 執行代碼生成器自動生成代碼 autoGenerator.execute(); }
-
運行代碼生成器
運行代碼生成器,然后我們會發現我們的項目多出了包和類,如下圖:
點進去類里,我們會發現它里面已經幫我們全部寫好了
-
編寫 Controller 接口
在我們自動生成的 UserController 類中增加一個hello 的接口
/** * <p> * 前端控制器 * </p> * * @author xp * @since 2020-08-01 */ @RestController @RequestMapping("/user") public class UserController { @Autowired UserService userService; @RequestMapping("/hello") public String hello(){ return userService.getById(2).toString(); } }
-
測試訪問接口
我們通過瀏覽器訪問我們剛剛編寫好的接口,輸出我們數據庫中的信息則代表代碼生成器已經自動生成成功了