一、Mybatis-Plus概述
1.1、Plus簡介
MyBatis-Plus(簡稱 MP)由baomidou(苞米豆)組織開發並且開源的一個項目,是一個 MyBatis 的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。
mybatis-plusde與mybatis的關系就像是魂斗羅P1玩家和P2玩家的關系,Mp的功能是基於Mybatis的,是在其基礎上做進一步的封裝和完善,在一定程度上簡化了sql的編寫,自動生成了基本的CRUD方法。基友搭配,效率翻倍
gitee:ttps://gitee.com/baomidou/mybatis-plus
GitHub:https://github.com/baomidou/mybatis-plus
文檔地址:https://mybatis.plus/guide/
1.2、MP的特性
-
無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
-
損耗小:啟動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作
-
強大的 CRUD 操作:內置通用 Mapper、通用 Service,僅僅通過少量配置即可實現單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求
-
支持 Lambda 形式調用:通過 Lambda 表達式,方便的編寫各類查詢條件,無需再擔心字段寫錯
-
支持多種數據庫:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多種數據庫
-
支持主鍵自動生成:支持多達 4 種主鍵策略(內含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問題
-
支持 XML 熱加載:Mapper 對應的 XML 支持熱加載,對於簡單的 CRUD 操作,甚至可以無 XML 啟動
-
支持 ActiveRecord 模式:支持 ActiveRecord 形式調用,實體類只需繼承 Model 類即可進行強大的 CRUD 操 作
-
支持自定義全局通用操作:支持全局通用方法注入( Write once, use anywhere )
-
支持關鍵詞自動轉義:支持數據庫關鍵詞(order、key......)自動轉義,還可自定義關鍵詞
-
內置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,支持模板引擎,更有超多自定義配置等您來使用
-
內置分頁插件:基於 MyBatis 物理分頁,開發者無需關心具體操作,配置好插件之后,寫分頁等同於普通 List查詢
-
內置性能分析插件:可輸出 Sql 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢
-
內置全局攔截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規則,預防誤操作
-
內置 Sql 注入剝離器:支持 Sql 注入剝離,有效預防 Sql 注入攻擊
1.3、MP的架構
二、快速開始(基於Springboot)
2.1、搭建環境
--建庫的sql語句
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID', `user_name` varchar(20) NOT NULL COMMENT '用戶名',
`password` varchar(20) NOT NULL COMMENT '密碼',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年齡',
`email` varchar(50) DEFAULT NULL COMMENT '郵箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 插入測試數據
INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('1', 'tom', '123456', '湯姆', '18', 'tom@qq.cn');
INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('2', 'rose', '123456', '柔絲', '20', 'rose@qq.cn');
INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('3', 'jack', '123456', '傑克', '28', 'jack@qq.cn');
INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('4', 'bill', '123456', '比爾', '21', 'bill@qq.cn');
INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('5', 'fack', '123456', '法克', '24', 'fake@qq.cn');
<!-- pom依賴 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--mybatis-plus依賴-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!--lombok依賴-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--數據庫驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
yaml配置:
spring:
datasource:
username: 'root'
password: 'root'
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&serverTimezone=Asia/Shanghai&useLegacyDatetimeCode=false
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
# configuration下的是mybatis的配置
configuration:
# 打印sql日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
在 Spring Boot 啟動類中添加 @MapperScan
注解,掃描 Mapper 文件夾:
@SpringBootApplication
@MapperScan("com..mybatisplus.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(QuickStartApplication.class, args);
}
}
編寫User.java實體類
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_user")
public class User extends Model<User> {
private Long id;
//注意userName的屬性和數據庫列不一樣,mysql中是user_name
private String userName;
private String password;
private String name;
private Integer age;
private String email;
}
Mapper類UserMapper.java
/**
Mapper接口需要繼承BaseMapper<>接口,這樣就完整了springboot和Mp的整合
*/
@Repository
public interface UserMapper extends BaseMapper<User> {
}
以上就完成了springboot正和MP的過程,和整合Mybatis基本沒有差異,唯獨Mapper接口繼承了BaseMapper
測試查詢所有:
@SpringBootTest
@RunWith(SpringRunner.class)
public class MybatisPlusDemo1 {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect1(){
List<User> userDemos = userMapper.selectList(null);
//Assert.assertEquals(5, studentUsers.size());
userDemos.forEach(System.out::println);
}
}
結果如下:
可以看到我們沒有寫Mapper.xml文件也沒通過注解的方式寫sql語句,直接調用selectList()方法就自動生成了查詢所有的sql,並自動處理了結果集,即便實體類的屬性有些和mysql的列字段有些不一致。
可以留意下User實體類會發現上面有個注解,這個注解指定了這個實體類與數據庫的哪張表關聯,我們所查詢的表也正是這張表。
三、注解
注解類包:👉 mybatis-plus-annotation
@TableName
- 描述:表名注解
常用的屬性就是value,指定表名。也可以在application.yaml中配置全局的表前綴
mybatis-plus:
global-config:
db-config:
#表名前綴,全局配置后可省略@TableName()配置
table-prefix: tb_
實體類是User,默認關聯的表名為user,配置全局的表前綴后,就關聯tb_user表
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 表名 |
schema | String | 否 | "" | schema |
keepGlobalPrefix | boolean | 否 | false | 是否保持使用全局的 tablePrefix 的值(如果設置了全局 tablePrefix 且自行設置了 value 的值) |
resultMap | String | 否 | "" | xml 中 resultMap 的 id |
autoResultMap | boolean | 否 | false | 是否自動構建 resultMap 並使用(如果設置 resultMap 則不會進行 resultMap 的自動構建並注入) |
@TableId
- 描述:主鍵注解
value指定主鍵在表中的列名,默認和屬性名相同。
id-type為主鍵的類型,也可以在yaml配置文件中配置全局的主鍵類型:
mybatis-plus:
global-config:
db-config:
#全局默認主鍵類型,設置后,即可省略實體對象中的@TableId(type = IdType.AUTO)配置。默認值ASSIGN_ID
id-type: auto
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
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 ) |
@TableField
- 描述:字段注解(非主鍵)
在MP中通過@TableField注解與數據庫表中非主鍵的字段關聯,常常解決的問題有2個:
1、對象中的屬性名和字段名不一致的問題(非駝峰)
2、對象中的屬性字段在表中不存在的問題(exist=false)
默認開啟了駝峰命名規則映射,也可以在yaml文件中配置關閉或者開啟
# 開啟駝峰命名規則映射,對象屬性為駝峰命令,表字段為_A_COLUMN(下划線命名)(默認也是打開的)
# 例如實體類userName字段關聯表中user_name的列
mybatis-plus:
# configuration下的是mybatis的配置
configuration:
map-underscore-to-camel-case: true
屬性 | 類型 | 必須指定 | 默認值 | 描述 |
---|---|---|---|---|
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.DEFAULT | 字段自動填充策略 |
select | boolean | 否 | true | 是否進行 select 查詢 |
keepGlobalFormat | boolean | 否 | false | 是否保持使用全局的 format 進行處理 |
jdbcType | JdbcType | 否 | JdbcType.UNDEFINED | JDBC類型 (該默認值不代表會按照該值生效) |
typeHandler | Class<? extends TypeHandler> | 否 | UnknownTypeHandler.class | 類型處理器 (該默認值不代表會按照該值生效) |
numericScale | String | 否 | "" | 指定小數點后保留的位數 |
FieldStrategy
值 | 描述 |
---|---|
IGNORED | 忽略判斷 |
NOT_NULL | 非NULL判斷 |
NOT_EMPTY | 非空判斷(只對字符串類型字段,其他類型字段依然為非NULL判斷) |
DEFAULT | 追隨全局配置 |
FieldFill
值 | 描述 |
---|---|
DEFAULT | 默認不處理 |
INSERT | 插入時填充字段 |
UPDATE | 更新時填充字段 |
INSERT_UPDATE | 插入和更新時填充字段 |
四、通用CRUD
UserMapper集成了BaseMapper接口,在接口中Mp預先定義了一些通用的CRUD
方法,使用這些方法不用寫SQL語句,Mp會自動為我們生成SQL。
4.1、插入操作
在實體類id上添加注解並指定類型為自動增長(也可以在yaml配置全局的類型)
@Test
public void testInsert(){
User user = new User();
user.setAge(20);
user.setEmail("caocao@qq.cn");
user.setName("曹操");
user.setUserName("caocao");
user.setPassword("123456");
int insert = userMapper.insert(user);
System.out.println("自增id:"+ user.getId());
}
結果如下,成功插入一條數據,並且還會返回最后一條記錄的主鍵自增值
如果將IdType.AUTO改為IdType.ASSIGN_ID,再插入數據
結果如下:id會通過雪花算法生成全球唯一的id
4.2、更新操作
4.2.1、根據id刪除
@Test
public void testUpdate(){
User user = new User();
user.setId(6l);
user.setEmail("liubei@qq.cn");
user.setName("劉備");
user.setUserName("liubei");
//更新不為null的字段
userMapper.updateById(user);
}
結果如下:不為空的字段進行更新
4.2..2、根據條件更新
@Test
public void testUpdate(){
User user = new User();
user.setAge(25);
//更新的條件
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("id", 6);
//更新不為null的字段
userMapper.update(user,wrapper);
//根據UpdateWrapper刪除
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", 1).set("age", 23);
//執行更新操作
int result = this.userMapper.update(null, updateWrapper);
}
結果如下:
QueryWrapper和UpdateWrapper都是Wrapper的實現類
4.3、刪除操作
@Test
public void testDel(){
//根據id刪除一條記錄
int i = userMapper.deleteById(5);
//批量刪除操作
int i1 = userMapper.deleteBatchIds(Arrays.asList(1311904394600452098l, 1311904394600452099l, 1311904394600452100l));
System.out.println("刪除"+i1+"條記錄");
//根據Map條件刪除
Map<String,Object> delCondition = new HashMap<>();
delCondition.put("id","3");
delCondition.put("name","jack");
int i2 = userMapper.deleteByMap(delCondition);
System.out.println("刪除"+i2+"條記錄");
//根據Wrapper刪除
QueryWrapper<User> wrapper = new QueryWrapper();
Map<String,Object> map = new HashMap<>();
map.put("id",1);
map.put("name","tom");
wrapper.allEq(map);
int delete = userMapper.delete(wrapper);
System.out.println("刪除數據:"+delete+"條");
}
結果如下:后面兩個刪除語句沒有刪除數據是因為沒有匹配的條件
4.4、查詢操作
@Test
public void testSelect(){
userMapper.selectBatchIds(Arrays.asList(1,2,3));
userMapper.selectById(2);
userMapper.selectCount(null);
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("id",1);
userMapper.selectOne(wrapper);
}
結果如下:
BaseMapper的查詢方法不僅僅四個,其中selectPage和selectMapsPage,為分頁查詢。
4.4.2、分頁查詢
分頁查詢需要配置插件
@Configuration
public class MybatisPlusConfig {
/**
* 新的分頁插件,一緩和二緩遵循mybatis的規則,需要設置 MybatisConfiguration#useDeprecatedExecutor = false
* 避免緩存出現問題(該屬性會在舊插件移除后一同移除)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
}
@Test
public void testPageSelect(){
//分頁查詢需要配置分頁插件
Page<User> page = new Page<>(1,3);
IPage<User> pages = userMapper.selectPage(page, null);
System.out.println("總記錄數:"+pages.getTotal());
System.out.println("總頁數"+pages.getSize());
pages.getRecords().forEach(System.out::println);
}
結果如下:Records代表查詢的集合,Total為總記錄數,size為總頁數
4.4.3、模糊查詢
MP的各種復雜的條件都需要使用Wrapper來實現
@Test
public void testLikeQuery(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("user_name","o");
wrapper.orderByDesc(true,"age");
List<User> userDemos = userMapper.selectList(wrapper);
userDemos.forEach(System.out::println);
}
結果如下:模糊查詢user_name中帶o的並將結果按年齡降序
五、SQL的注入原理
MP在啟動后會將BaseMapper中的一系列的方法注冊到meppedStatements中,其中ISqlInjector接口就是負責Sql的注入工作,AbstractSqlInjector是其實現類。
inject方法調用了injectMappedStatement方法
injectMappedStatement是一個抽象方法,它由具體的實現類實現
每一個被自動注入的方法都有一個類對其進行注入,以SelectList類為例
六、Mp的基本配置
Mybatis-Plus中有許多的配置有一部分是mybatis原生的配置,有一部分是mybatis的配置(mybatis-plus.configuration下的配置就是mybatis的配置)更過配置請查看官方文檔
mybatis-plus:
# configuration下的是mybatis的配置
configuration:
# 打印sql日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 開啟駝峰命名規則映射,對象屬性為駝峰命令,表字段為_A_COLUMN(下划線命名)
map-underscore-to-camel-case: true
# 緩存開啟(默認開啟)
cache-enabled: true
# 這下面的是mybatis-plus的配置,對應配置類MybatisPlusProperties
# mapper文件的位置,Maven 多模塊項目的掃描路徑需以 classpath*: 開頭 (即加載多個 jar 包下的 XML 文件)
mapper-locations: classpath*:/mapper/*.xml
# config-location: # mybatis配置文件的位置
# pojo包下起別名
type-aliases-package: com.mybatisplus.pojo
# 全局配置
global-config:
#db策略配置
db-config:
#全局默認主鍵類型,設置后,即可省略實體對象中的@TableId(type = IdType.AUTO)配置。默認值ASSIGN_ID
id-type: auto
#表名前綴,全局配置后可省略@TableName()配置
table-prefix: tb_
七、條件構造器Wrapper
Wrapper接口的各種實現類如下:
-
eq:等於 =
-
allEq:
-
ne:不等於 <>
-
gt:大於 >
-
ge:大於等於 >=
-
lt:小於 <
-
le:小於等於 <=
-
between:BETWEEN 值1 AND 值2
-
notBetween:NOT BETWEEN 值1 AND 值2
-
isNull: is null
-
isNotNull: is not null
-
or:or(各個條件之間默認使用and連接)
-
in:字段 IN (value.get(0), value.get(1), ...)
-
notIn:字段 NOT IN (v0, v1, ...)
-
like: like'%值%'
-
likeLeft: like '%值'
-
likeRight: like '值%'
-
groupBy:分組
-
orderBy:排序