Mybatis-plus简介
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
润物无声
只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。
效率至上
只需简单配置,即可快速进行 CRUD 操作,从而节省大量时间。
丰富功能
热加载、代码生成、分页、性能分析等功能一应俱全。
依赖
注意:引入 MyBatis-Plus 之后请不要再次引入 MyBatis,以避免因版本差异导致的问题。
同时我们还需要安装Lombok插件。
<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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok用来简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
配置
在 application.properties /yml配置文件中添加 MySQL 数据库的相关配置:
我的MySQL数据库版本是8 ,所以需要配置jdbc8驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
yml格式
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=UTC
username: root
password: 123456
启动类
在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹
@SpringBootApplication
@MapperScan("com.zenghao.demomptest.mapper")
public class DemomptestApplication {
public static void main(String[] args) {
SpringApplication.run(DemomptestApplication.class, args);
}
}
添加实体
创建包 entity 编写实体类 User.java(此处使用了 Lombok 简化代码)
@Data
public class User {
private Long id
private String name
private Integer age
private String email
}
添加mapper
创建包 mapper 编写Mapper 接口: UserMapper.java
@Repository
public interface UserMapper extends BaseMapper<User> {
}
测试
添加测试类,进行功能测试:
@SpringBootTest
class DemomptestApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void findAll() {
List<User> users = userMapper.selectList(null);
System.out.println(users);
}
}
注意:
IDEA在 userMapper 处报错,因为找不到注入的对象,因为类是动态创建的,但是程序可以正确的执行。
为了避免报错,可以在 dao 层 的接口上添加 @Repository 注
通过以上几个简单的步骤,我们就实现了 User 表的 CRUD 功能,甚至连 XML 文件都不用编写!
查看控制台输出:

查看sql输出日志
#日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
主键策略
插入操作
*//添加
\ @Test
public void testAdd() {
User user = new User();
user.setName("lucy");
user.setAge(20);
user.setEmail("1243@qq.com");
int insert = userMapper.insert(user);
System.out.println(insert);
}
注意:数据库插入id值默认为:全局唯一id

MP的主键策略
ASSIGN_ID
MyBatis-Plus默认的主键策略是:ASSIGN_ID (使用了雪花算法)
@TableId(type = IdType.ASSIGN_ID)
private String id;
雪花算法:分布式ID生成器
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
核心思想:
长度共64bit(一个long型)。
首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。
10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。

优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。
AUTO 自增策略
需要在创建数据表的时候设置主键自增
实体字段中配置 @TableId(type = IdType.AUTO)
@TableId(type = IdType.AUTO)
private Long id;
要想影响所有实体的配置,可以设置全局主键配置
#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto
自动填充和乐观锁
更新操作
注意:update时生成的sql自动是动态sql:UPDATE user SET age=? WHERE id=?
*//修改
@Test
public void testUpdate() {
User user = new User();
user.setId(1340868235401764865L);
user.setName("lucymary");
int count = userMapper.updateById(user);
System.out.println(count);
}
自动填充
需求描述:
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。
我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作
数据库修改
在User表中添加datetime类型的新的字段 create_time、update_time
实体类修改
实体上增加字段并添加自动填充注解
@TableField(fill = FieldFill.INSERT\)
private Date createTime; //create_time
@TableField(fill = FieldFill.INSERT_UPDATE\)
private Date updateTime; *//update_time*
实现元对象处理器接口
注意:不要忘记添加 @Component 注解
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//mp执行添加操作,这个方法执行
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//mp执行修改操作,这个方法执行
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
乐观锁
场景
主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新
乐观锁实现方式:
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败
接下来介绍如何在Mybatis-Plus项目中,使用乐观锁:
乐观锁实现流程
修改实体类
添加 @Version 注解
@Version
private Integer version;
创建配置文件
创建包config,创建文件MybatisPlusConfig.java
此时可以删除主类中的 @MapperScan 扫描注解
@Configuration
@MapperScan("com.zenghao.demomptest.mapper")
public class MpConfig {
*/**
\* 乐观锁插件
\*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
注册乐观锁插件
在 MybatisPlusConfig 中注册 Bean
/**
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
查询
通过多个id批量查询
完成了动态sql的foreach的功能
*//多个id批量查询
\@Test
public void testSelect1() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
System.out.println(users);
}
简单的条件查询
通过map封装查询条件
注意:map中的key对应数据库中的列名。如:数据库user_id,实体类是userId,这时map的key需要填写user_id
*//简单条件查询
@Test
public void testSelect2() {
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("name","Jack");
columnMap.put("age",20);
List<User> users = userMapper.selectByMap(columnMap);
System.out.println(users);
}
分页
分页插件
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
添加分页插件
配置类中添加@Bean配置
*/**
\* 分页插件
\*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
测试selectPage分页
测试:最终通过page对象获取相关数据
*//分页查询
@Test
public void testSelectPage() {
Page<User> page = new Page(1,3);
Page<User> userPage = userMapper.selectPage(page, null);
//返回对象得到分页所有数据
long pages = userPage.getPages(); *//总页数
long current = userPage.getCurrent(); *//当前页
List<User> records = userPage.getRecords(); *//查询数据集合
long total = userPage.getTotal(); *//总记录数
boolean hasNext = userPage.hasNext(); *//下一页
boolean hasPrevious = userPage.hasPrevious(); *//上一页
System.out.println(pages);
System.out.println(current);
System.out.println(records);
System.out.println(total);
System.out.println(hasNext);
System.out.println(hasPrevious);
}
测试selectMapsPage分页
当指定了特定的查询列时,希望分页结果列表只返回被查询的列,而不是很多null值
测试selectMapsPage分页:结果集是Map
@Test
public void testSelectMapsPage() {
//Page不需要泛型
Page<Map<String, Object>> page = newPage<>(1, 5);
Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, null);
List<Map<String, Object>> records = pageParam.getRecords();
records.forEach(System.out::println);
System.out.println(pageParam.getCurrent());
System.out.println(pageParam.getPages());
System.out.println(pageParam.getSize());
System.out.println(pageParam.getTotal());
System.out.println(pageParam.hasNext());
System.out.println(pageParam.hasPrevious());
}
删除与逻辑删除
删除
根据id删除记录
@Test
public void testDeleteById(){
int result = userMapper.deleteById(5L);
system.out.println(result);
}
批量删除
@Test
public void testDeleteBatchIds() {
int result = userMapper.deleteBatchIds(Arrays.asList(8, 9, 10));
system.out.println(result);
}
简单条件删除
@Test
public void testDeleteByMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "Helen");
map.put("age", 18);
int result = userMapper.deleteByMap(map);
system.out.println(result);}
逻辑删除
物理删除和逻辑删除
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
逻辑删除的使用场景:
可以进行数据恢复
有关联数据,不便删除
逻辑删除实现流程
数据库修改
添加 deleted字段
ALTERTABLE `user` ADD COLUMN `deleted` boolean DEFAULT false
实体类修改
添加deleted 字段,并加上 @TableLogic 注解
@TableLogic private Integer deleted;
配置(可选)
application.properties 加入以下配置,此为默认值,如果你的默认值和mp默认的一样,该配置可无
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
条件构造器和常用接口
wapper介绍

Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : 查询条件封装
UpdateWrapper : Update 条件封装
AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper : Lambda 更新封装Wrapper
查询方式:
| 查询方式 | 说明 |
|---|---|
| setSqlSelect | 设置 SELECT 查询字段 |
| where | WHERE 语句,拼接 + WHERE 条件 |
| and | AND 语句,拼接 + AND 字段=值 |
| andNew | AND 语句,拼接 + AND (字段=值) |
| or | OR 语句,拼接 + OR 字段=值 |
| orNew | OR 语句,拼接 + OR (字段=值) |
| eq | 等于= |
| allEq | 基于 map 内容等于= |
| ne | 不等于<> |
| gt | 大于> |
| ge | 大于等于>= |
| lt | 小于< |
| le | 小于等于<= |
| like | 模糊查询 LIKE |
| notLike | 模糊查询 NOT LIKE |
| in | IN 查询 |
| notIn | NOT IN 查询 |
| isNull | NULL 值查询 |
| isNotNull | IS NOT NULL |
| groupBy | 分组 GROUP BY |
| having | HAVING 关键词 |
| orderBy | 排序 ORDER BY |
| orderAsc | ASC 排序 ORDER BY |
| orderDesc | DESC 排序 ORDER BY |
| exists | EXISTS 条件语句 |
| notExists | NOT EXISTS 条件语句 |
| between | BETWEEN 条件语句 |
| notBetween | NOT BETWEEN 条件语句 |
| addFilter | 自由拼接 SQL |
| last | 拼接在最后,例如:last(“LIMIT 1”) |
测试用例
@SpringBootTest
public class MybatisPlusWrapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void test01(){
//查询用户名包含a,年龄在20-30之间,邮箱信息不为null的用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("user_name","a")
.between("age",20,30)
.isNotNull("email");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
@Test
public void test02(){
//查询用户信息,按照年龄的降序,若年龄相同,则按照id的升序排序
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("uid");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
@Test
public void test03(){
//删除邮箱地址为null的用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int i = userMapper.delete(queryWrapper);
System.out.println("result:"+i);
}
@Test
public void test04(){
//将(年龄大于20并且用户名中包含a)或邮箱为null的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",20)
.like("user_name","a")
.or()
.isNull("email");
User user = new User();
user.setName("小明");
user.setEmail("2283134870@qq.com");
int update = userMapper.update(user, queryWrapper);
System.out.println("result:"+update);
}
@Test
public void test05(){
//将用户名中包含有a并且(年龄大于20或邮箱为null) 的用户信息修改
//lambda中的表达式优先执行
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("user_name","a")
.and(i->i.gt("age",20).or().isNull("email"));
User user = new User();
user.setName("小红");
user.setEmail("2283134870@qq.com");
int update = userMapper.update(user, queryWrapper);
System.out.println("result:"+update);
}
@Test
public void test06(){
//查询用户的用户名,年龄,邮箱信息
//SELECT user_name,age,email FROM t_user WHERE is_deleted=0
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("user_name","age","email");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
/**
* 子查询
*/
@Test
public void test07(){
//查询id小于等于100的用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("uid","select uid from t_user where uid <= 10");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
@Test
public void test08(){
//将用户名中包含有a并且(年龄大于20或邮箱为null) 的用户信息修改
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.like("user_name","a")
.and(i -> i.gt("age",20).or().isNull("email"));
updateWrapper.set("user_name","小黑").set("email","787878.com");
int i = userMapper.update(null, updateWrapper);
System.out.println("rs:" + i);
}
@Test
public void test09(){
String username = "a";
Integer ageBegin = 20;
Integer ageEnd = 30;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if(StringUtils.isNotBlank(username)){
//isNotBlank判断某个字符串是否不为空字符串,不为null,不为空白符
queryWrapper.like("user_name",username);
}
if (ageBegin != null){
queryWrapper.ge("age",ageBegin);
}
if (ageEnd != null){
queryWrapper.le("age",ageEnd);
}
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
@Test
public void test10(){
String username = "a";
Integer ageBegin = 20;
Integer ageEnd = 30;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username),"user_name",username)
.ge(ageBegin != null,"age",ageBegin)
.le(ageEnd != null,"age",ageEnd);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
@Test
public void test11() {
String username = "a";
Integer ageBegin = 20;
Integer ageEnd = 30;
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username),User::getName,username)
.ge(ageBegin != null,User::getAge,ageBegin)
.le(ageEnd != null,User::getAge,ageEnd);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
}