MyBatis-Plus 基礎
簡介
MyBatis-Plus (opens new window)(簡稱 MP)是一個 MyBatis (opens new window)的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。
-
無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑。
-
損耗小:啟動即會自動注入基本 CURD,效能基本無損耗,直接物件導向操作。
-
強大的 CRUD 操作:內建通用 Mapper、通用 Service,僅僅通過少量配置即可實現單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求。
-
支援 Lambda 形式呼叫:通過 Lambda 表示式,方便的編寫各類查詢條件,無需再擔心欄位寫錯。
-
支援主鍵自動生成:支援多達 4 種主鍵策略(內含分散式唯一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問題。
-
支援 ActiveRecord 模式:支援 ActiveRecord 形式呼叫,實體類只需繼承 Model 類即可進行強大的 CRUD 操作。
-
內建程式碼生成器:採用程式碼或者 Maven 外掛可快速生成 Mapper 、 Model 、 Service 、 Controller 層程式碼,支援模板引擎,更有超多自定義配置等您來使用。
-
內建分頁外掛:基於 MyBatis 物理分頁,開發者無需關心具體操作,配置好外掛之後,寫分頁等同於普通 List 查詢。
-
分頁外掛支援多種資料庫:支援 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多種資料庫。
-
內建效能分析外掛:可輸出 SQL 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢。
-
內建全域性攔截外掛:提供全表 delete 、 update 操作智慧分析阻斷,也可自定義攔截規則,預防誤操作。
入門
建立資料庫和表
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`user_id` int NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
`username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '使用者名稱',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密碼',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '0-正常,1-刪除',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '建立時間',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改時間',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '建立者',
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
`version` int NULL DEFAULT NULL COMMENT '樂觀鎖',
PRIMARY KEY (`user_id`) USING BTREE,
INDEX `user_idx1_username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '使用者表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', '123456', '0', '2021-08-29 00:38:43', '2021-08-29 00:38:45', 'admin', 'admin', 1);
INSERT INTO `sys_user` VALUES (2, 'loner', '123456', '0', '2021-08-29 00:38:43', '2021-08-29 00:38:45', 'admin', 'admin', 1);
INSERT INTO `sys_user` VALUES (3, 'Bob', '123456', '0', '2021-08-29 00:38:43', '2021-08-29 00:38:45', 'admin', 'admin', 1);
INSERT INTO `sys_user` VALUES (4, 'Jack', '123456', '0', '2021-08-29 00:38:43', '2021-08-29 00:38:45', 'admin', 'admin', 1);
匯入相關依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--MyBatis-Plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.4.3</version>
</dependency>
<!--Druid資料來源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<!--MySQL驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
編寫配置檔案
# 資料來源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.11:3306/mybatis-plus?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
username: root
password: MaH00...
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
使用和測試
// 建立使用者實體表
@Data
@TableName("sys_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 使用者ID
*/
@TableId(value = "user_id", type = IdType.AUTO)
private Long userId;
/**
* 使用者賬號
*/
private String username;
/**
* 密碼
*/
private String password;
/**
* 刪除標誌(0代表存在 2代表刪除)
*/
@TableLogic
private String delFlag;
/**
* 建立者
*/
@TableField(fill = FieldFill.INSERT)
private String createBy;
/**
* 建立時間
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/**
* 更新時間
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 樂觀鎖
*/
private Integer version;
}
// 繼承BaseMapper介面
public interface UserMapper extends BaseMapper<User> {
}
// 編寫測試程式碼
@SpringBootTest
public class UserTest {
@Autowired
private UserMapper userMapper;
@Test
public void test() {
// 直接使用BaseMapper定義好的查詢方法
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}
}
整個MP(簡稱)的使用過程,就是如此的簡單,當我們繼承完 BaseMapper 介面後,我們就完成了 CRUD 操作,剩餘的只是針對不同的業務進行封裝使用罷了。
MyBatis-Plus 核心
CRUD 介面
Mapper CRUD 介面
通用 CRUD 封裝 BaseMapper (opens new window)介面,為 Mybatis-Plus 啟動時自動解析實體表關係對映轉換為 Mybatis 內部物件注入容器,泛型 T 為任意實體物件,引數 Serializable 為任意型別主鍵,Mybatis-Plus 不推薦使用複合主鍵約定每一張表都有自己的唯一 id 主鍵。
// 插入一條記錄
int insert(T entity);
// 根據 ID 刪除
int deleteById(Serializable id);
// 根據 entity 條件,刪除記錄
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 刪除(根據ID 批量刪除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根據 columnMap 條件,刪除記錄
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根據 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);
// 根據 whereWrapper 條件,更新記錄
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
// 根據 ID 查詢
T selectById(Serializable id);
// 根據 entity 條件,查詢一條記錄
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查詢(根據ID 批量查詢)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根據 entity 條件,查詢全部記錄
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查詢(根據 columnMap 條件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根據 Wrapper 條件,查詢全部記錄
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根據 Wrapper 條件,查詢全部記錄。注意: 只返回第一個欄位的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根據 entity 條件,查詢全部記錄(並翻頁)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根據 Wrapper 條件,查詢全部記錄(並翻頁)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根據 Wrapper 條件,查詢總記錄數
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
Service CRUD 介面
通用 Service CRUD 封裝 IService (opens new window)介面,進一步封裝 CRUD,採用 get 查詢單行,remove 刪除 ,list 查詢集合,page 分頁,字首命名方式區分 Mapper 層避免混淆,泛型 T 為任意實體物件。
建議如果存在自定義通用 Service 方法的可能,請建立自己的 IBaseService 繼承 Mybatis-Plus 提供的基類
物件 Wrapper 為 條件構造器。
Save
// 插入一條記錄(選擇欄位,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量),bathSize指插入批次數量
boolean saveBatch(Collection<T> entityList, int batchSize);
Remove
// 根據 entity 條件,刪除記錄
boolean remove(Wrapper<T> queryWrapper);
// 根據 ID 刪除
boolean removeById(Serializable id);
// 根據 columnMap 條件,刪除記錄
boolean removeByMap(Map<String, Object> columnMap);
// 刪除(根據ID 批量刪除)
boolean removeByIds(Collection<? extends Serializable> idList);
Update
// 根據 UpdateWrapper 條件,更新記錄 需要設定sqlset
boolean update(Wrapper<T> updateWrapper);
// 根據 whereWrapper 條件,更新記錄
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根據 ID 選擇修改
boolean updateById(T entity);
// 根據ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根據ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);
SaveOrUpdate
// TableId 註解存在更新記錄,否插入一條記錄
boolean saveOrUpdate(T entity);
// 根據updateWrapper嘗試更新,否繼續執行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
Get
// 根據 ID 查詢
T getById(Serializable id);
// 根據 Wrapper,查詢一條記錄。結果集,如果是多個會丟擲異常,隨機取一條加上限制條件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根據 Wrapper,查詢一條記錄
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根據 Wrapper,查詢一條記錄
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根據 Wrapper,查詢一條記錄
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
List
// 查詢所有
List<T> list();
// 查詢列表
List<T> list(Wrapper<T> queryWrapper);
// 查詢(根據ID 批量查詢)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查詢(根據 columnMap 條件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查詢所有列表
List<Map<String, Object>> listMaps();
// 查詢列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查詢全部記錄
List<Object> listObjs();
// 查詢全部記錄
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根據 Wrapper 條件,查詢全部記錄
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根據 Wrapper 條件,查詢全部記錄
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
Page
// 無條件分頁查詢
IPage<T> page(IPage<T> page);
// 條件分頁查詢
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 無條件分頁查詢
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 條件分頁查詢
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
條件構造器
基本條件
// 等於,例如 eq("name", "老王") 等價於 name = '老王'
eq(R column, Object val)
// 不等於,例如 ne("name", "老王") 等價於 name <> '老王'
ne(R column, Object val)
// 大於,例如 gt("age", 18) 等價於 age > 18
gt(R column, Object val)
// 小於,例如 lt("age", 18) 等價於 age < 18
lt(R column, Object val)
// 大於等於,例如 ge("age", 18) 等價於 age >= 18
ge(R column, Object val)
// 小於等於,例如 le("age", 18) 等價於 age <= 18
le(R column, Object val)
// BETWEEN 值1 AND 值2,例如 between("age", 18, 30) 等價於 age between 18 and 30
between(R column, Object val1, Object val2))
// NOT BETWEEN 值1 AND 值2,例如 notBetween("age", 18, 30) 等價於 age not between 18 and 30
notBetween(R column, Object val1, Object val2)
// LIKE '%值%',例如 like("name", "王") 等價於 name like '%王%'
like(R column, Object val)
// NOT LIKE '%值%',例如 notLike("name", "王") 等價於 name not like '%王%'
notLike(R column, Object val)
// LIKE '%值',例如 likeLeft("name", "王") 等價於 name like '%王'
likeLeft(R column, Object val)
// LIKE '值%',例如 likeRight("name", "王") 等價於 name like '王%'
likeRight(R column, Object val)
// 欄位 IS NULL,例如 isNull("name") 等價於 name is null
isNull(R column)
// 欄位 IS NOT NULL,例如 isNotNull("name") 等價於 name is not null
isNotNull(R column)
拼接巢狀
// 欄位 IN (v0, v1, ...)
in(R column, Object... values) // in("age", 1, 2, 3) 等價於 age in (1,2,3)
// 欄位 IN (value.get(0), value.get(1), ...)
in(R column, Collection<?> value) // 例如 in("age",{1,2,3}) 等價於 age in (1,2,3)
// 欄位 NOT IN (v0, v1, ...)
notIn(R column, Object... values) // 例如 notIn("age", 1, 2, 3) 等價於 age not in (1,2,3)
// 欄位 NOT IN (value.get(0), value.get(1), ...)
notIn(R column, Collection<?> value) // 例如 notIn("age",{1,2,3}) 等價於 age not in (1,2,3)
// 欄位 IN ( sql語句 ),同理還有 欄位 NOT IN (SQL 語句)
inSql(R column, String inValue) // // 例如:inSql("id", "select id from table where id < 3") 等價於 id in (id < 3)
// 拼接 OR,注意:主動呼叫or表示緊接著下一個方法不是用and連線!(不呼叫or則預設為使用and連線)
or(boolean condition) // 例如:eq("id",1).or().eq("name","老王") 等價於 id = 1 or name = '老王'
// OR 巢狀
or(Consumer<Param> consumer) // 例如:or(i -> i.eq("name", "李白").ne("status", "活著")) 等價於 or (name = '李白' and status <> '活著')
// AND 巢狀
and(Consumer<Param> consumer) // 例如:and(i -> i.eq("name", "李白").ne("status", "活著")) 等價於 and (name = '李白' and status <> '活著')
// 正常巢狀 不帶 AND 或者 OR
nested(Consumer<Param> consumer) // 例如:nested(i -> i.eq("name", "李白").ne("status", "活著")) 等價於 (name = '李白' and status <> '活著')
// 無視優化規則直接拼接到 sql 的最後,只能呼叫一次,多次呼叫以最後一次為準,有sql注入的風險,請謹慎使用
last(String lastSql) // 例如:last("limit 1"),這是唯一的可以在SQL中使用limit語句的,並且只能是QueryWrapper構造器。
last(boolean condition, String lastSql)
// 拼接 EXISTS ( sql語句 )
exists(String existsSql) // 例如:exists("select id from table where age = 1") 等價於 exists (select id from table where age = 1)
// 拼接 NOT EXISTS ( sql語句 )
notExists(String notExistsSql) // 例如:notExists("select id from table where age = 1") 等價於 not exists (select id from table where age = 1)
// 拼接 SQL,該方法可用於資料庫函式,動態入參的params對應前面applySql內部的{index}部分,這樣是不會有sql注入風險的,反之會有!
apply(String applySql, Object... params)
// 例如: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'")
分組排序
// 分組:GROUP BY 欄位, ...
groupBy(R... columns) // 例如:groupBy("id", "name") 等價於 group by id,name
// 排序:ORDER BY 欄位, ... [規則]
orderByAsc(R... columns) // 例如:orderByAsc("id", "name") 等價於 order by id ASC,name ASC
orderByDesc(R... columns) // 例如:orderByDesc("id", "name") 等價於 order by id DESC,name DESC
orderBy(boolean condition, boolean isAsc, R... columns) // 例如:orderBy(true, true, "id", "name") 等價於 order by id ASC,name ASC
// HAVING ( sql語句 )
having(String sqlHaving, Object... params) // 例如:having("sum(age) > 10") 等價於 having sum(age) > 10
// 函式方法,主要方便在出現if...else下呼叫不同方法能不斷鏈
func(Consumer<Children> consumer) // 例如: func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})