文章目錄
一、概述
雖然mybatis可以直接在xml中通過SQL語句操作數據庫,很是靈活。但正因其操作都要通過SQL語句進行,就必須寫大量的xml文件,很是麻煩。mybatis-plus就很好的解決了這個問題。
官方的定義:Mybatis-Plus
(簡稱MP
)是一個 Mybatis 的增強工具,在 Mybatis 的基礎上只做增強不做改變,爲簡化開發、提高效率而生。這是官方給的定義,關於mybatis-plus的更多介紹及特性,可以參考mybatis-plus官網。那麼它是怎麼增強的呢?其實就是它已經封裝好了一些crud方法,我們不需要再寫xml了,直接調用這些方法就行,就類似於JPA。
-
文檔地址:https://mp.baomidou.com/guide/
官方文檔已經寫的很詳細了,本文主要是對官方文檔的一個自我學習的總結
二、準備工作
這裏使用的是Spring Boot 和 Mysql
1. 添加依賴
在Spring Boot 工程中添加 Mybatis-Plus 依賴(不需要添加Mybatis的依賴了)和Mysql驅動包依賴:
Maven倉庫中的MybatisPlus依賴:https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok簡化代碼-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2. 配置數據庫
在application.yaml文件中配置數據庫
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=CTT
username: root
password: 123
3. 添加@MapperScan註解
在 Spring Boot 啓動類中添加 @MapperScan 註解,掃描 Mapper 文件夾:
@SpringBootApplication
@MapperScan("com.test.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(QuickStartApplication.class, args);
}
}
4. 編寫實體類
實體類中會需要用到一些註解,之後再詳細的說。
@Data
@TableName("t_user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
5. 編寫Mapper接口
編寫 Mapper 接口時,繼承 BaseMapper<T>
父接口,泛型 T
是這個 Mapper 對應的實體類, BaseMapper<T>
父接口中已經將通用的 CRUD 封裝好了,直接使用即可,後面也會說。
public interface UserMapper extends BaseMapper<User> {
}
至此,一個簡單的 Mybatis-Plus
已經集成完畢
三、實體類中的註解
1. @TableName(表名註解)
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 指定表名(當表名與實體類名不一致(不滿足駝峯和下劃線的映射關係)時,用該屬性指定實體類對應的表名) |
2. @TableId(主鍵註解)
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 主鍵字段名 |
type | Enum | 否 | IdType.NONE | 主鍵類型 |
IdType(主鍵類型)
值 | 描述 |
---|---|
AUTO | 數據庫ID自增 |
NONE | 該類型爲未設置主鍵類型(將跟隨全局) |
INPUT | 插入前手動輸入 |
ID_WORKER | 全局唯一ID (idWorker、雪花算法)(只有當插入對象ID 爲空,才自動填充)(默認) |
UUID | 全局唯一ID (UUID)(只有當插入對象ID 爲空,才自動填充) |
ID_WORKER_STR | 字符串全局唯一ID (idWorker 的字符串表示)(只有當插入對象ID 爲空,才自動填充) |
3. @TableField(非主鍵字段註解)
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 指定數據庫字段名(與@TableName註解中的value屬性類似,只不過這個是指定非主鍵字段名) |
exist | boolean | 否 | true | 是否爲數據庫表字段 |
fill | Enum | 否 | FieldFill.DEFAULT | 字段自動填充策略(可以設置更新或者插入時自動填充) |
四、BaseMapper<T>中的方法
1. 插入
// 插入一條記錄
int insert(T entity);
-
參數說明:
類型 參數名 描述 T entity 實體對象
2. 刪除
// 根據 wrapper 條件,刪除記錄
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 根據ID 的集合批量刪除
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根據 ID 刪除
int deleteById(Serializable id);
// 根據 columnMap 條件,刪除記錄
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
-
參數說明:
類型 參數名 描述 Wrapper wrapper 實體對象封裝操作類(可以爲 null) Collection<? extends Serializable> idList 主鍵ID列表(不能爲 null 以及 empty) Serializable id 主鍵ID Map<String, Object> columnMap 表字段 map 對象,鍵是表的列名
3. 更新
// 根據 updateWrapper 條件,更新記錄
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
// 根據 ID 修改,如果entity中的某個字段爲空,則這個字段就不會出現在sql語句中
int updateById(@Param(Constants.ENTITY) T entity);
-
參數說明:
類型 參數名 描述 T entity 實體對象 (set 條件值,可爲 null) Wrapper updateWrapper 實體對象封裝操作類(可以爲 null,裏面的 entity 用於生成 where 語句)
4. 查詢
-
只查詢一個:
// 根據 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); // 查詢(根據某些列的值查詢),注意:map中的鍵是表的列名 List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); // 根據 Wrapper 條件查詢,返回回來的是 map 而不是實體類 List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根據 Wrapper 條件,查詢全部記錄。注意: 只返回第一個字段的值 List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
-
分頁查詢(詳見“九、分頁查詢”):
// 根據 queryWrapper 條件進行分頁查詢,要配置分頁插件。使用方法詳見 "九、分頁查詢" // 第一個形參page:可以使用Page對象,new Page(current, size),current表示當前頁,size表示每頁顯示條數 IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 同上一個方法,返回值是map IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根據 Wrapper 條件,查詢總記錄數 Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
-
參數說明:
類型 參數名 描述 Serializable id 主鍵ID Wrapper queryWrapper 實體對象封裝操作類(可以爲 null) Collection<? extends Serializable> idList 主鍵ID列表(不能爲 null 以及 empty) Map<String, Object> columnMap 表字段 map 對象 IPage page 分頁查詢條件(可以爲 RowBounds.DEFAULT,也可以使用Page對象)
五、條件構造器
這部分官方文檔已經寫的很清楚了,也比較簡單,我就說一下常用的好了。
官方文檔:https://mp.baomidou.com/guide/wrapper.html#abstractwrapper
-
常用的
AbstractWrapper
父類方法,該父類用於生成 sql 的 where 條件:注意:下面的每個方法,基本上都有一個重載方法,這個重載方法都是在形參的一個參數位置,增加了:
boolean condition
形參(其餘參數後移一位),用來表示該條件是否加入最後生成的sql中eq(R column, Object val)//等於, 例: eq("name", "老王")--->name = '老王' ne(R column, Object val)//不等於 gt(R column, Object val)//大於 ge(R column, Object val)//>= lt(R column, Object val)//< le(R column, Object val)//<= between(R column, Object val1, Object val2)//BETWEEN 值1 AND 值2 notBetween(R column, Object val1, Object val2)//NOT BETWEEN 值1 AND 值2 like(R column, Object val)//LIKE '%值%' notLike(R column, Object val)//NOT LIKE '%值%' likeLeft(R column, Object val)//LIKE '%值' likeRight(R column, Object val)//LIKE '值%' isNull(R column)//字段 IS NULL isNotNull(R column)//字段 IS NOT NULL in(R column, Collection<?> value)//字段 IN (value.get(0), value.get(1), ...) in(R column, Object... values)//字段 IN (v0, v1, ...) inSql(R column, String inValue)//字段 IN ( sql語句 ) notIn(R column, Collection<?> value)//字段 NOT IN (value.get(0), value.get(1), ...) notIn(R column, Object... values)//字段 NOT IN (v0, v1, ...) notInSql(R column, String inValue)//字段 NOT IN ( sql語句 ) groupBy(R... columns)//分組:GROUP BY 字段, ... having(String sqlHaving, Object... params)//HAVING ( sql語句 ); 例: having("sum(age) > {0}", 11)--->having sum(age) > 11 orderBy(boolean condition, boolean isAsc, R... columns)//排序:ORDER BY 字段, ... orderByAsc(R... columns)//排序:ORDER BY 字段, ... ASC orderByDesc(R... columns)//排序:ORDER BY 字段, ... DESC or()//拼接 OR; 例: eq("id",1).or().eq("name","老王")--->id = 1 or name = '老王' or(Consumer<Param> consumer)//OR 嵌套; 例: or(i -> i.eq("name", "李白").ne("status", "活着"))--->or (name = '李白' and status <> '活着') and(Consumer<Param> consumer)//AND 嵌套; 例: and(i -> i.eq("name", "李白").ne("status", "活着"))--->and (name = '李白' and status <> '活着') nested(Consumer<Param> consumer)//正常嵌套 不帶 AND 或者 OR apply(String applySql, Object... params)//拼接 sql,使用動態拼接參數不會有SQL注入的風險 last(String lastSql)//無視優化規則直接拼接到 sql 的最後,只能調用一次,多次調用以最後一次爲準,有sql注入的風險,請謹慎使用 exists(String existsSql)//拼接 EXISTS ( sql語句 ) notExists(String notExistsSql)//拼接 NOT EXISTS ( sql語句 )
-
QueryWrapper
繼承了AbstractWrapper
,並新增了select()
方法QueryWrapper(T entity)//在創建 QueryWrapper 對象時,可以傳入一個實體類,默認是以 = 爲條件 select(String... sqlSelect)//選擇要查詢的字段,例:select("id", "name", "age")
-
UpdateWrapper
繼承了AbstractWrapper
,並新增了set()
和setSql()
方法UpdateWrapper(T entity)//在創建 UpdateWrapper 對象時,可以傳入一個實體類,默認是以 = 爲條件 set(String column, Object val)//SQL SET 字段; 例: set("name", "老李頭") setSql(String sql)//設置 SET 部分 SQL; 例: setSql("name = '老李頭'")
這裏舉幾個例子:
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//SELECT name,id,age FROM user WHERE age BETWEEN ? AND ? AND ( name = ? ) OR email LIKE ?
queryWrapper.select("name", "id", "age")
.between("age", 20, 31)
.nested(i -> i.eq("name", "lele"))
.or()
.like("email", "@qq");
//SELECT id,name,age,email FROM user WHERE name like ? and age > ? limit 2
queryWrapper.select("id", "name", "age", "email")
.apply("name like {0} and age > {1}", "%l%", 19)
.last("limit 2");
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
//UPDATE user SET age=? WHERE name = ?
updateWrapper.set("age",23)
.eq("name", "lele");
六、常用配置
1. 表字段駝峯命名與下劃線映射
mybatis-plus.configuration.mapUnderscoreToCamelCase
是否開啓自動駝峯命名規則(camel case
)映射,即從經典數據庫列名 A_COLUMN
(下劃線命名) 到經典 Java 屬性名 aColumn
(駝峯命名) 的類似映射。如果數據庫的命名符合規則,則無需使用 @TableField 註解指定數據庫字段名
- 類型:boolean
- 默認值:true(默認開啓)
2. 打印SQL語句日誌配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
七、自動填充插件
表中的字段如:創建時間、修改時間這些字段,可以使用下面兩種方法來實現自動填充
1. 數據庫級別
對數據庫中這兩個字段進行如下設置:
CREATE TABLE `mytest` (
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在創建時間字段的時候
-
DEFAULT CURRENT_TIMESTAMP
:表示當插入數據的時候,該字段默認值爲當前時間 -
ON UPDATE CURRENT_TIMESTAMP
:表示每次更新這條數據的時候,該字段都會更新成當前時間
2. 代碼級別(使用MybatisPlus)
一般工作中不允許修改數據庫,這裏就可以使用MybatisPlus提供的方法,共兩步:
-
在實體類中需要自動填充的字段上加入註解
@TableField(.. fill = FieldFill.INSERT)
// 字段添加填充內容 @TableField(fill = FieldFill.INSERT)//表示插入時自動填充 private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE)//表示插入和更新時自動填充 private Date updateTime;
-
編寫處理器,實現元對象處理器接口:
com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
@Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推薦使用) this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 該方法有bug請升級到之後的版本如`3.3.1.8-SNAPSHOT`) /* 上面選其一使用,下面的已過時(注意 strictInsertFill 有多個方法,詳細查看源碼) */ //this.setFieldValByName("operator", "Jerry", metaObject); //this.setInsertFieldValByName("operator", "Jerry", metaObject); } @Override public void updateFill(MetaObject metaObject) { log.info("start update fill ...."); this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推薦使用) this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 該方法有bug請升級到之後的版本如`3.3.1.8-SNAPSHOT`) /* 上面選其一使用,下面的已過時(注意 strictUpdateFill 有多個方法,詳細查看源碼) */ //this.setFieldValByName("operator", "Tom", metaObject); //this.setUpdateFieldValByName("operator", "Tom", metaObject); } }
八、樂觀鎖插件
以官方文檔爲主:樂觀鎖插件
MybatisPlus 實現樂觀鎖要兩個步驟:
-
在實體類的字段上添加
@Version
註解注意:
- 支持的數據類型只有:
int
,Integer
,long
,Long
,Date
,Timestamp
,LocalDateTime
- 整數類型下
newVersion = oldVersion + 1
newVersion
會回寫到entity
中- 僅支持
updateById(id)
與update(entity, wrapper)
方法
@Version private Integer version;
- 支持的數據類型只有:
-
添加一個攔截器
@Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); }
小例子:
// 1、查詢用戶信息,這裏查出來version爲2
User user = userMapper.selectById(1);
// 2、修改用戶信息
user.setName("一名小碼農");
user.setAge("25");
// 3、執行更新操作
userMapper.updateById(user);
上面的代碼相當於下面的 sql 語句:
# 先查詢用戶信息
select * from tbl_user where id=1;
# 更新
update tbl_user set name = '一名小碼農', age = 25, version = 3 where id = 1 and version = 2
九、分頁插件
官方文檔:https://mp.baomidou.com/guide/page.html
-
配置一個分頁攔截器:
PaginationInterceptor
://Spring boot方式 @Configuration public class MybatisPlusConfig { @Bean public 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; } }
-
然後直接使用分頁查詢即可:
// 參數一:當前頁 // 參數二:頁面大小 Page<User> page = new Page<>(2, 5); userMapper.selectPage(page, null); //查詢後的結果會放在傳入的 Page 對象中 page.getRecords().forEach(System.out::println); //total是Page對象的屬性,表示總條數 System.out.println(page.getTotal());
Page對象
Page 對象的屬性:
字段名 | 字段類型 | 說明 | 默認值 |
---|---|---|---|
records | List<T> | 查詢出的數據列表 | 空集合 |
total | long | 總數 | 0 |
size | long | 每頁顯示條數 | 10 |
current | long | 當前頁 | 1 |
orders | List<OrderItem> | 排序字段信息 | 空集合 |
optimizeCountSql | boolean | 自動優化 COUNT SQL | true |
isSearchCount | boolean | 是否進行 count 查詢 | true |
hitCount | boolean | 是否命中count緩存 | false |
常用 Page 對象方法:
方法名 | 返回值 | 說明 |
---|---|---|
hasPrevious() | boolean | 是否存在上一頁 |
hasNext() | boolean | 是否存在下一頁 |
十、邏輯刪除
3.3.0 版本之前的邏輯刪除需要添加攔截器,3.3.0 版本開始,只需要做一些簡單的配置即可
- application.yml 加入配置:
mybatis-plus: global-config: db-config: logic-delete-field: flag #全局邏輯刪除字段值 3.3.0開始支持,詳情看下面。 logic-delete-value: 1 # 邏輯已刪除值(默認爲 1) logic-not-delete-value: 0 # 邏輯未刪除值(默認爲 0)
- 實體類字段上加上
@TableLogic
註解@TableLogic private Integer deleted;
邏輯刪除,最終操作數據庫的 SQL 語句都是更新操作
查詢的時候,MybatisPlus 會自動拼接上 @TableLogic 修飾的字段,這樣邏輯刪除的數據就不會顯示出來了
十一、代碼生成
代碼生成可以使用官方的代碼:https://mp.baomidou.com/guide/generator.html
也可以使用 Easycode 來生成代碼:https://blog.csdn.net/leisurelen/article/details/103930429/
也可以使用下面的代碼用來生成代碼:
- 添加代碼生成依賴:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.2</version>
</dependency>
- 使用代碼生成器(裏面有的地方需要根據實際情況修改)
// 代碼自動生成器
class GeneratorCodeTest {
public static void main(String[] args) {
// 需要構建一個 代碼自動生成器 對象
AutoGenerator mpg = new AutoGenerator();
// 配置策略
// 1、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("狂神說");
gc.setOpen(false);
gc.setFileOverride(false); // 是否覆蓋
gc.setServiceName("%sService"); // 去Service的I前綴
gc.setIdType(IdType.ID_WORKER);
gc.setDateType(DateType.ONLY_DATE);
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
//2、設置數據源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/kuang_community? useSSL = false & useUnicode = true & characterEncoding = utf - 8 & serverTimezone = GMT % 2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
//3、包的配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("blog");
pc.setParent("com.kuang");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
//4、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("blog_tags", "course", "links", "sys_settings", "user_record", "user_say"); // 設置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true); // 自動lombok;
strategy.setLogicDeleteFieldName("deleted");
// 自動填充配置
TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(gmtCreate);
tableFills.add(gmtModified);
strategy.setTableFillList(tableFills);
// 樂觀鎖
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true); //localhost:8080 / hello_id_2
mpg.setStrategy(strategy);
mpg.execute(); //執行
}
}