57. MP 使用

SpringBoot 版本区别

SpringBoot 2.0.x 和 SpringBoot 2.1.x 使用不同:SpringBoot 2.1.x 版本中书写数据库驱动的和数据路径和 2.0.x 中不一样。 2.1.x 中使用的 mysql 驱动是 com.mysql.cj.jdbc.Driver,并在 url 后面需要配置 ?serverTimezone=GMT%2B8 来解决时区问题。

SpringBoot 1.x 和 SpringBoot 2.x 的区别:SpringBoot 1.x 底层使用的是 Spring 4.x,而 SpringBoot 2.x 底层使用的是 Spring 5.x。Spring 5.x 中的注解功能比 Spring 4.x 中的更加强大。

MP 简单示例

代码地址

官网:http://mp.baomidou.com/,参考教程:http://mp.baomidou.com/guide/,[MyBatis-Plus](https://github.com/baomidou/mybatis-plus)(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

创建数据库及表信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);

INSERT INTO user (id, name, age, email)
VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

创建 mbp-demo 项目,并引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok用来简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

添加配置文件

1
2
3
4
5
6
7
# 配置连接 
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
# 打印日志(例如 SQL 语句)
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

在 IDEA 中安装 lombok 插件, 并测试查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.yanrs.mpbdemo;

import com.yanrs.mpbdemo.entity.User;
import com.yanrs.mpbdemo.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
class MpbDemoApplicationTests {
@Autowired
private UserMapper userMapper;

@Test
void testSelectList() {
// 查询所有 user 信息
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}

主键生成策略

分布式系统唯一ID生成方案汇总:https://www.cnblogs.com/haoxinyue/p/5208136.html

MyBatis-Plus默认的主键策略是:ID_WORKER 全局唯一ID【也就是上面链接中提到的 snowflake 算法生成的】。如果想手动指定,那么可以在实体类的需要应用的字段上进行设置。

1
2
3
4
5
6
7
8
@Data
public class User {
@TableId(type = IdType.ID_WORKER)
private Long id;
private String name;
private Integer age;
private String email;
}

上面的设置只是针对一个实体类进行设置的,如果要进行全局的设置,那么可以在配置文件中进行如下配置

1
mybatis-plus.global-config.db-config.id-type=auto

IdType 的取值,也就是设置主键的 ID 有以下几种方案:

1
2
3
4
5
6
AUTO 自动增长
NONE 为空
INPUT 需要手工输入,不是自动生成
ID_WORKER snowflake算法生成的ID
UUID UUID 生成的ID
ID_WORKER_STR snowflake算法生成的字符串ID

插入示例

1
2
3
4
5
6
7
8
9
10
@Test
void addUser(){
User user = new User();
user.setAge(10);
user.setEmail("zhangsan@gmail.com");
user.setName("zhangsan");

int insert = userMapper.insert(user);
System.out.println(insert);
}

自动填充

数据库表结构新增 create_time,update_time 两个字段,默认值为 null。如果数据库中这两个字断没有设置默认值,那么数据的插入和修改的时候这两个值都是空的。我们可以使用 mp 来指定字段,在 insert 或者 update 的时候进行值的填充。

实体类上添加注解 @TableField 表明是在什么时候进行填充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
public class User {
@TableId(type = IdType.ID_WORKER)
private Long id;
private String name;
private Integer age;
private String email;
// 插入的时候对值进行填充
@TableField(fill = FieldFill.INSERT)
private Date createTime;
// 插入或者更新的时候对值进行填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}

新建 handler 包,并新建一个类 customMpMateObjectHandler 实现 MetaObjectHandler 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.yanrs.mpbdemo.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class customMpMateObjectHandler implements MetaObjectHandler {
// 在执行 insert 操作的时候运行
@Override
public void insertFill(MetaObject metaObject) {
// 在进行 insert 操作的时候为 createTime updateTime 设置新的值
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}

// 在执行 update 操作的时候运行
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}

再次运行 insert 新增一个用户,就会发现数据库中 create_time,update_time 两个字段都有了值。修改也是 update_time 的值也发生了变化。

1
2
3
4
5
6
7
8
9
10
@Test
void addUser(){
User user = new User();
user.setAge(10);
user.setEmail("lisi@gmail.com");
user.setName("lisi");

int insert = userMapper.insert(user);
System.out.println(insert);
}
1
2
3
4
5
6
7
8
9
@Test
void updateUser(){
User user = new User();
user.setId(1271700428140089345L);
user.setName("new name");
// 更新 id 为 1271700428140089345 的 name 为 new name,并观察 update_time
int i = userMapper.updateById(user);
System.out.println(i);
}

乐观锁

主要适用场景:当更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新。乐观锁解决的是 “丢失更新” 的问题。“丢失更新” 是指,事务中当对同一条记录进行修改的时候(事务中对同一条记录进行读可能出现脏读,不可重复读和幻读,写则可能出现丢失更新),例如 A 将数据库中的值修改为 300,然后提交了事务,同时 B 将又将数据库中的值改为 500,也提交了事务。最后数据库中的值是 500 ,但是对于 A 来说,就是 “丢失更新”。

解决丢失更新的方案是使用乐观锁,在数据库中新增 version 字段,A,B 在进行更改前获取该 version 字段的值,提交事务的时候判断自己获取的 version 值和数据库中的是否一致,一致则提交事务,并将 version 值加一,否则本次修改失败。整个过程如下:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

数据库中新增 version 字段,并在实体类中新增 version 属性,使用自动填充当数据插入时设置 version 的默认值为1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.yanrs.mpbdemo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.util.Date;

@Data
public class User {
@TableId(type = IdType.ID_WORKER)
private Long id;
private String name;
private Integer age;
private String email;
// 插入的时候对值进行填充
@TableField(fill = FieldFill.INSERT)
private Date createTime;
// 插入或者更新的时候对值进行填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

// 乐观锁 version 字段
@TableField(fill = FieldFill.INSERT)
@Version
private Integer version;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.yanrs.mpbdemo.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class customMpMateObjectHandler implements MetaObjectHandler {
// 在执行 insert 操作的时候运行
@Override
public void insertFill(MetaObject metaObject) {
// 在进行 insert 操作的时候为 createTime updateTime 设置新的值
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);

// 为乐观锁 version 字段设置默认值
this.setFieldValByName("version", 1, metaObject);
}

// 在执行 update 操作的时候运行
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}

新增 MP 乐观锁插件的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yanrs.mpbdemo.config;

import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@MapperScan("com.yanrs.mpbdemo.mapper")
@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig {

/**
* 乐观锁插件
* @return
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}

新增一条数据,然后对其进行修改操作,测试数据库中 version 字段的变化。会发现 version 字段的值会从 1 变成2。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
void addUser(){
User user = new User();
user.setAge(10);
user.setEmail("wangwu@gmail.com");
user.setName("wangwu");

int insert = userMapper.insert(user);
System.out.println(insert);
}

@Test
void testOptimisticLocker(){
// 查询用户 version 信息
User user = userMapper.selectById(1271729094970646529L);
// 更新用户信息
user.setName("new Name");
int i = userMapper.updateById(user);
System.out.println(i);
}

简单查询

根据 ID 进行查询

1
2
3
4
5
@Test
void testSelectById(){
User user = userMapper.selectById(1271729094970646529L);
System.out.println(user);
}

批量查询

1
2
3
4
5
@Test
void testSelectBatchIds(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1271729094970646529L, 1271700428140089345L));
System.out.println(users);
}

根据 Map 进行查询

1
2
3
4
5
6
7
8
9
@Test
void testSelectByMap(){
HashMap<String, Object> condition = new HashMap<>();
condition.put("name", "zhangsan");
// 注意:map中的key对应的是数据库中的列名。例如数据库user_id,实体类是userId,这时map的key需要填写user_id
condition.put("create_time", new Date());
List<User> users = userMapper.selectByMap(condition);
System.out.println(users);
}

分页查询

配置分页插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.yanrs.mpbdemo.config;

import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@MapperScan("com.yanrs.mpbdemo.mapper")
@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig {

/**
* 乐观锁插件
* @return
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}

/**
* 分页插件
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}

分页方式1,返回的是查询的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
void testPageObject(){
// 第一个参数是当前页,第二个参数为每页大小
Page<User> page = new Page<>(1, 3);
// 第一个参数是 page 对象,第二是查询条件,这里是对所有数据分页,所有 null 就行
userMapper.selectPage(page, null);

System.out.println(page.getCurrent()); // 当前页
System.out.println(page.getPages()); // 可以分多少页
System.out.println(page.getRecords()); // 分页数据
System.out.println(page.getSize()); // 每页数据大小
System.out.println(page.getTotal()); // 总记录数
System.out.println(page.hasNext()); // 是否有下一页
System.out.println(page.hasPrevious()); // 是否有上一页
}

分页方式2,返回的是 map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
void testPageMap(){
// 第一个参数是当前页,第二个参数为每页大小
Page<User> page = new Page<>(1, 3);
// 第一个参数是 page 对象,第二是查询条件,这里是对所有数据分页,所有 null 就行
userMapper.selectMapsPage(page, null);

System.out.println(page.getCurrent()); // 当前页
System.out.println(page.getPages()); // 可以分多少页
System.out.println(page.getRecords()); // 分页数据
System.out.println(page.getSize()); // 每页数据大小
System.out.println(page.getTotal()); // 总记录数
System.out.println(page.hasNext()); // 是否有下一页
System.out.println(page.hasPrevious()); // 是否有上一页
}

简单删除

根据 ID 删除记录

1
2
3
4
@Test
void testDeleteById(){
userMapper.deleteById(1L);
}

批量删除

1
2
3
4
@Test
void testDeleteBatchIds(){
userMapper.deleteBatchIds(Arrays.asList(2L, 3L));
}

条件删除

1
2
3
4
5
6
@Test
void testDeleteByMap(){
HashMap<String, Object> condition = new HashMap<>();
condition.put("name", "zhangsan");
userMapper.deleteByMap(condition);
}

逻辑删除

数据库新增 deleted 字段,并在实体类中新增 deleted 属性,配置该字段为逻辑删除字段,并在配置类中配置逻辑删除。

在实体类中新增 deleted 属性,配置该字段注解 TableLogic 表示为逻辑删除字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.yanrs.mpbdemo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.util.Date;

@Data
public class User {
@TableId(type = IdType.ID_WORKER)
private Long id;
private String name;
private Integer age;
private String email;

// 插入的时候对值进行填充
@TableField(fill = FieldFill.INSERT)
private Date createTime;

// 插入或者更新的时候对值进行填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

// 乐观锁 version 字段
@TableField(fill = FieldFill.INSERT)
@Version
private Integer version;

// 逻辑删除
@TableField(fill = FieldFill.INSERT)
@TableLogic
private Integer deleted;
}

在插入数据的时候,自动为 deleted 字段设置值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.yanrs.mpbdemo.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class customMpMateObjectHandler implements MetaObjectHandler {
// 在执行 insert 操作的时候运行
@Override
public void insertFill(MetaObject metaObject) {
// 在进行 insert 操作的时候为 createTime updateTime 设置新的值
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);

// 为乐观锁 version 字段设置默认值
this.setFieldValByName("version", 1, metaObject);

// 逻辑删除
this.setFieldValByName("deleted", 1, metaObject);
}

// 在执行 update 操作的时候运行
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}

在配置类中加上逻辑删除插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.yanrs.mpbdemo.config;

import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@MapperScan("com.yanrs.mpbdemo.mapper")
@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig {

/**
* 乐观锁插件
* @return
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}

/**
* 分页插件
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}

/**
* 逻辑删除插件
*/
@Bean
public ISqlInjector iSqlInjector(){
return new LogicSqlInjector();
}
}

在 properties 配置文件中配置逻辑删除和非逻辑删除的标识

1
2
3
# 逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=0
mybatis-plus.global-config.db-config.logic-not-delete-value=1

新增数据

1
2
3
4
5
6
7
8
9
10
@Test
void addUser(){
User user = new User();
user.setAge(10);
user.setEmail("zhaoliu@gmail.com");
user.setName("zhaoliu");

int insert = userMapper.insert(user);
System.out.println(insert);
}

测试逻辑删除

1
2
3
4
5
6
@Test
void testDeleteByMapLogic(){
HashMap<String, Object> condition = new HashMap<>();
condition.put("name", "zhaoliu");
userMapper.deleteByMap(condition);
}

注意⚠️:如果再运行查询,那么查询到的数据是没有被逻辑删除的,这是因为 MP 插件自动为我们处理了。如果要查询所有的数据(包含被逻辑删除了的和没有被逻辑删除了的)那么就得自己写 SQL 进行查询。

条件查询

查询方式 说明
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”)

例子

1
2
3
4
5
6
7
8
9
10
11
@Test
void testQueryWrapper(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("name", "age");
queryWrapper.eq("name", "zhangsan");
queryWrapper.gt("age", 12);
List<User> users = userMapper.selectList(queryWrapper);
for (User user:users) {
System.out.println(user);
}
}