一、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接口,获取一些通用的CRUD操作。
测试查询所有:
@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:排序