MyBatis-Plus的基本使用

本文最后更新于:2023年11月9日 晚上

MyBatis-Plus的基本使用

使用MyBatis-Plus能够带来的好处是什么呢?MyBatis-Plus和MyBatis是一个怎样的关系?

1
2
3
使用MyBatis-Plus好处是可以节省非常多代码量,少写SQL、还可以代码生成、从而加快我们在开发中的效率(拒绝加班、拒绝996
MyBatis-Plus是对MyBatis框架进行了更强的封装,核心当然还是MyBatis。相当于在MyBatis的基础上新增了许多在实际开发中经常被使用到的功能
并且完全的简化了这些功能的实现代码,比如:分页、代码生成、乐观锁、逻辑删除等等...更好的帮助像我这种CRUD工程师进行高效率的项目开发

以上描述完全是个人理解,为了小伙伴们更好的学习MyBati-Plus可参考官方文档:https://baomidou.com

继续往下将带大家从Mybati-Plus的入门到常用的核心功能

1.mybatis-Plus快速入门体验

开发环境:SpringBoot 2.3.4+Mybatis-Plus 3.4.1+MySQL 8.0

注:不同的MyBatis-Plus版本可能在某些功能的配置上有所不同

  1. 创建数据库表并插入测试数据
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');
  1. 新建一个SpringBoot项目并在pom.xml文件中导入所需的Maven依赖
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
<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>
</dependency>

<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>

<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
  1. 在application.yaml文件中配置数据源

    1
    2
    3
    4
    5
    6
    spring:
    datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=Asia/Shanghai
    username: root
    password: libo
  2. 创建User实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Data
    /*全部参数的构造方法*/
    @AllArgsConstructor
    /*无参构造*/
    @NoArgsConstructor
    /*指定数据库表名*/
    @TableName(value = "user")
    /*实体类*/
    public class User {

    private Long id;

    private String name;

    private Integer age;

    private String email;
    }

  3. 创建userMapper接口并继承BaseMapper接口
    别忘了在启动类上加入@MapperScan(value = “com.mybatiPlus.mapper”) //扫描mapper文件

    1
    2
    3
    4
    5
    6
    @Repository
    /*Mapper接口*/
    public interface userMapper extends BaseMapper<User> {


    }
  4. 使用MyBatis-Plus操作数据(这里就测试下查询user表的所有数据)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @SpringBootTest
    /*测试类*/
    public class mybatisPlusTest {

    /*注入持久层userMapper接口*/
    @Autowired
    private userMapper userMapper;

    /*查询user表中所有数据*/
    @Test
    public void seleteByList() {
    /*Wrapper是一个条件构造器,这里暂且先不适用*/
    List<User> userList = userMapper.selectList(null);
    /*打印List*/
    userList.forEach(System.out::println);
    }

    控制台打印的结果:
    的确是把user表中的所有数据查询出来了,然而我们并没有编写SQL语句对吧,这就是MyBatis-Plus的强大之处。

这里就有两个问题:1.userMapper下的方法哪里来的?2.SQL是谁写的?
方法来自BaseMapper,因为我们继承了他并且传入了泛型。SQL语句当然是MyBatis-Plus中封装的啦,这只是MyBatis-Plus强大功能中的其中之一

2.配置日志输出

  1. 在application.yaml中加入
    1
    2
    3
    mybatis-plus:
    configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  2. 再次测试查询所有方法,然后观察控制台与之前多了哪些东西?

3.主键生成策略

什么是主键生成策略呢?在数据库中一张表的主键一般就是我们的id字段,但是主键字段的值就有很多种了。
比如常见的id自增,或者说是insert into插入数据的时候手动填入,那在MyBatis-Plus中又给我们带来哪些不同的生成策略呢?

  1. 插入一条数据到user表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /*添加数据*/
    @Test
    public void insert() {
    User user = new User();
    /*名字*/
    user.setName("周杰伦");
    /*年龄*/
    user.setAge(18);
    /*email*/
    user.setEmail("abc@163.com");
    /*添加数据*/
    userMapper.insert(user);
    }
  2. 查看日志输出,看MyBabtis-Plus默认给我们生成的id是什么?
  3. 查看数据库

问题:为什么会生成这么长一串id呢?或者说这个id是根据什么生成的呢?下面就介绍MyBatis的主键生成策略

MyBatis-Plus中不同的主键生成策略:

1
2
3
4
5
6
1. IdType.AUTO:数据库自增id(数据库表也必须设置为自增id)
2. IdType.NONE:该类型为未设置主键类型(和下面这个差不多)
3. IdType.INPUT:用户输入ID(自定义id)
4. IdType.ASSIGN_UUID:生成全局唯一UUID 注:主键字段为字符串
5. IdType.ID_WORKER:默认的全局唯一id (雪花算法) MyBatis-Plus默认策略
6. IdType.ID_WORKER_STR:是ID_WORKER的字符串表示法 (雪花算法)

注:IdType是一个枚举类

所以我们刚刚插入的数据生成的id就是使用MyBatis-Plus默认的生成策略IdType.ID_WORKER,其中采用了雪花算法。
主键生成策略的详细介绍可参考:分布式系统主键id生成策略

如果想要使用不同的策略需要在实体类的主键字段上加上注解 @TableId(type = IdType.ASSIGN_UUID)
比如我们再测试一个自动生成的UUID,因为UUID包含字母那就要把主键id字段改为String类型,数据库主键字段类型为varchar

1
2
3
4
5
6
7
8
@TableId(type = IdType.ASSIGN_UUID)
private String id;

private String name;

private Integer age;

private String email;


再次执行我们之前的添加数据方法,查看结果


记得将实体类主键类型和数据库表主键字段类型改回来,上面只做一个测试。

4.单表删除操作

1
2
3
4
5
6
/*根据id删除数据*/
@Test
public void delete() {
/*根据id删除*/
userMapper.deleteById(1342413749983252482L);
}


另外再补充两个不同的删除方式:

根据id批量删除

1
2
3
4
5
6
7
/*根据id批量删除*/
@Test
public void deleteByList() {
/*批量删除id为1、2的数据*/
List<Long> longs = Arrays.asList(1L, 2L);
userMapper.deleteBatchIds(longs);
}

根据Map条件删除

1
2
3
4
5
6
7
8
/*根据Map条件删除*/
@Test
public void deleteByMap() {
Map<String, Object> map = new HashMap();
/*删除name为周杰伦的数据*/
map.put("name", "周杰伦");
userMapper.deleteByMap(map);
}

5.单表逻辑删除

我们之前删除数据是直接从数据库表中删除的,删除之后表里面这条数据就没有了,对吧
那么逻辑删除就是:要删除的数据并不是真正的从表中删除,只是在查询时不展示这条数据即可。
例如就相当于回收站:被删除的数据在回收站,但是回收站并没有清空,我们只是判断它没有作用了,放到一边而已

如何实现:

  1. 数据库表新增一个字段并且给一个默认值
    假设我们设置逻辑删除字段的值默认为0
  2. 实体类增加逻辑删除字段配上注解
    加上注解:@TableLogic
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Data
    /*全部参数的构造方法*/
    @AllArgsConstructor
    /*无参构造*/
    @NoArgsConstructor
    /*指定数据库表名*/
    @TableName(value = "user")
    public class User {

    @TableId(type = IdType.ID_WORKER)
    private Long id;

    private String name;

    private Integer age;

    private String email;

    /*逻辑删除*/
    @TableLogic
    private Integer delete;
    }
  3. 在application.yaml文件中配置MyBatisPlus逻辑删除组件
    1
    2
    3
    4
    5
    6
    7
    mybatis-plus:
    #逻辑删除
    global-config:
    db-config:
    logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
    logic-delete-value: 1 # 逻辑已删除值(默认为 1)
    logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  4. 配置完成之后让我们来测试一个删除方法
    1
    2
    3
    4
    5
    6
    /*根据id删除数据*/
    @Test
    public void delete() {
    /*根据id删除*/
    userMapper.deleteById(3L);
    }

    逻辑删除其实走的是update方法,把逻辑删除字段的值给修改了,我们设置的是未删除时字段默认为0,逻辑删除后字段为1
    然后在查询时加上条件deleted = 0,所以在查询时deleted字段为1的值我们并没有查询出来,只查询了值为0的数据

6.单表更新操作

现在想把id为1的数据中name改为刘德华,看看应该如何操作?

1
2
3
4
5
6
7
8
9
10
11
/*更新数据*/
@Test
public void update() {
User user = new User();
/*id*/
user.setId(1L);
/*修改名字*/
user.setName("刘德华");
/*根据id修改*/
userMapper.updateById(user);
}

查看日志执行的SQL:

查看数据库表:

上面只是修改了一个name字段,如果修改多个字段呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*更新数据*/
@Test
public void update() {
User user = new User();
/*id*/
user.setId(1L);
/*修改名字*/
user.setName("刘德华");
/*修改年龄*/
user.setAge(20);
/*修改邮箱*/
user.setEmail("123@qq.com");
/*根据id修改*/
userMapper.updateById(user);
}

注意看执行的SQL语句,和上面修改一个字段相比较,相当于在修改数据时MyBatis-Plus会做一个实体类字段的的非空验证
实体类中不为空的字段就视为要修改的属性。

7.单表查询操作

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
/*查询user表中所有数据*/
@Test
public void seleteByTest() {
/*Wrapper是一个条件构造器,这里暂且先不使用*/
List<User> userList = userMapper.selectList(null);
/*打印List*/
userList.forEach(System.out::println);
}

/*根据id查询一个*/
@Test
public void seleteByid() {
/*查询id为5的数据*/
User user = userMapper.selectById(5);
/*输出*/
System.out.println(user);
}


/*根据id批量查询*/
@Test
public void selectBatchIds() {
List<Integer> list = Arrays.asList(3, 4, 5);
/*查询多个id*/
List<User> users = userMapper.selectBatchIds(list);
/*输出所有*/
users.forEach(System.out::println);
}


/*条件查询*/
@Test
public void selectByMap() {
Map<String, Object> map = new HashMap();
/*查询name为周杰伦的数据*/
map.put("name", "周杰伦");
List<User> users = userMapper.selectByMap(map);
/*输出所有*/
users.forEach(System.out::println);
}

8.分页查询

一般分页的几种方式:

  1. 自己创建分页类,然后limit查询进行分页
  2. PageHelper插件分页

MyBatis-Plus这么强大当然也提供了分页插件,那我们就看看在MyBatis-Plus中如何使用分页?

首先在SpringBoot中创建一个配置类,然后添加方法注册一个Bean对象。这个配置类还会配置其他关于MyBatis-Plus的插件

1
2
3
4
5
6
7
8
9
10
11
/*配置类*/
@Component
public class myBatisPlusConfig {

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

}

在测试类中新建一个方法进行对分页的测试:
注:Page 对象中有很多方法,比如:数据总数,总页数等等。

1
2
3
4
5
6
7
8
9
10
/*分页查询*/
@Test
public void testPage() {
/*参数一:多少页(页码) 参数二:多少条数据(页面大小)*/
Page page = new Page(1, 1);
page = userMapper.selectPage(page, null);

/*输出数据*/
page.getRecords().forEach(System.out::println);
}

9.自动填充功能

在开发中一张表起码有这样的两个字段吧,一个是数据的插入时间,还有一个就是数据的修改时间。
所谓自动填充就是数据在插入、修改的时候给具体的字段设置值。例如设置当前时间

其实这种实现有两种方式:
注:不管采用什么样的方式实现都必须有这两个字段

  1. 数据库级别(不推荐使用)

数据的创建时间字段默认一栏写入CURRENT_TIMESTAMP,该字段就会在新增数据时自动填入当前时间(创建数据时填入该字段)

只需要勾选根据当前时间戳更新,该数据在修改时,updateTime字段就会自动填入当前时间(修改数据时更新该字段)

  1. 代码级别

如果使用第一种方法,我们在开发中不一定能有权限去修改数据库,非专业数据库工程师去操作也不太好
MyBatis-Plus给我们提供了代码级别的填充。

  • 可以先看下这个枚举类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入时填充字段
*/
INSERT,
/**
* 更新时填充字段
*/
UPDATE,
/**
* 插入和更新时填充字段
*/
INSERT_UPDATE
}
  • 在实体类种创建两个字段:

    1
    2
    3
    4
    5
    6
    7
    /*创建时间*/
    @TableField(fill = FieldFill.INSERT)
    private Date cteateTime;

    /*修改时间*/
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
  • 创建一个类来继承 MetaObjectHandler,该类作为MyBatis-Plus的自动填充处理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Slf4j
@Component
/*自动填充处理类*/
public class MyMetaObjectHandler implements MetaObjectHandler {

/*在插入数据时填充时间,两个字段都填充cteateTime和updateTime*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.setFieldValByName("cteateTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}

/*在插入数据时填充时间,只填充updateTime*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
  • 至此在数据的插入时cteateTime和updateTime两个字段都会被填充,在数据修改时updateTime字段会被填充。

10.乐观锁

乐观锁的实现方式:这里需要多增加一个字段,这里取名为version(版本)

  1. 先查询出当前数据的version字段
  2. 修改时带上这个version字段
  3. 执行修改的SQL语句时:set version = 新version where version = 旧version
  4. 如果第一次取出的version和修改时的version不同,那么就修改失败。修改成功那么就在旧version的值上+1

其实这里就是线程并发的问题:
那我们假设现在有AB两个线程,两个线程都要去执行修改同一条数据的操作,并且该数据的version字段为0,A线程执行时去查询值为0,B线程执行时查询值也为0。但是A线程先执行,那么version字段+1,那么现在version已经为1了,线程B去执行修改时发现 两次的version不一样,查询时为0,执行时为1,那么B线程就修改失败

看看MyBatis-Plus如何实现乐观锁:
数据库表和实体类都要新增一个version字段,并且在实体类字段上加上@Version注解,数据库字段给一个默认值

1
2
@Version
private Integer version;

在SpringBoot的代码中注入一个Bean对象

1
2
3
4
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}

11.wrapper 条件构造器

上面的SQL案例都是些比较简单的数据库操作,但是如何有很复杂的条件查询应该在MyBatis-Plus中如何使用?
这里就需要用到wrapper条件构造器,下面给大家举几个复杂条件的例子,wrapper 的功能远不止这些,参考官方文档进行编写

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*wrapper构造器*/
@SpringBootTest(classes = MyBatisPlusApplication.class)
public class wrapperTest {

/*注入持久层userMapper接口*/
@Autowired
private userMapper userMapper;


/*查询name和email不为null并且age大于等于18的数据*/
@Test
public void test(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper
.isNotNull("name")
.isNotNull("email")
.ge("age",18);
userMapper.selectList(wrapper).forEach(System.out::println);
}


/*查询name等于周杰伦的数据*/
@Test
public void test2(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.eq("name","周杰伦");
userMapper.selectList(wrapper).forEach(System.out::println);
}


/*查询age在20到30之间的数据*/
@Test
public void test3(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.between("age",20,30);//区间
Integer integer = userMapper.selectCount(wrapper);//查询结果数
System.out.println(integer);
}


/*模糊查询*/
@Test
public void test4(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.
notLike("name","李")//not like
.likeLeft("email","123");//%123

List<Map<String, Object>> map = userMapper.selectMaps(wrapper);
map.forEach(System.out::println);
}


/*子查询*/
@Test
public void test5(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.inSql("id","select id from user where id < 5");

List<Object> list = userMapper.selectObjs(wrapper);
list.forEach(System.out::println);
}


/*根据id倒叙*/
@Test
public void test6(){
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.orderByDesc("id");

List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
}

在不同的遭遇里我发现你的瞬间,有种不可言说的温柔直觉。

MyBatis-Plus的基本使用
http://example.com/2020/12/25/MyBatis-Plus的基本使用/
作者
阿波~
发布于
2020年12月25日
更新于
2023年11月9日
许可协议