MyBatisPlus

快速开始

通过sql创建数据库和表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DROP TABLE IF EXISTS `user`;

CREATE TABLE `user`
(
id BIGINT NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);

DELETE FROM `user`;

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
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
</dependency>

配置数据库

1
2
3
4
5
6
server.port=8080

spring.datasource.url=jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹:

1
2
3
4
5
6
7
@SpringBootApplication
@MapperScan("com.zhou.mybatis_plus.mapper")
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}

编写实体类 User.java

1
2
3
4
5
6
7
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}

编写 Mapper 接口类 UserMapper.java

1
2
3
4
5
6
// 在对应的Mapper上面继承基本的类 BaseMapper
@Repository // 代表持久层
public interface UserMapper extends BaseMapper<User> {
// 所有的CRUD操作都已经完成了
// 不需要像以前一样配置很多东西了
}

添加测试类,进行功能测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;

@Test
void contextLoads() {
//查询所有用户
//参数是一个wrapper,条件构造器,这里我们不用 那就是null
List<User> users= userMapper.selectList(null);

users.forEach(System.out::println);
}
}

就可以查询到所有user的数据

1
2
3
4
5
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)

配置日志

我们现在所有的sql是不可见的,我们需要知道是什么实现的

配置实现过程在控制台上输出

1
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

再次运行 我们就可以得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@244418a] was not registered for synchronization because synchronization is not active
2024-07-12 16:09:17.916 INFO 27212 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2024-07-12 16:09:18.369 INFO 27212 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1254651534 wrapping com.mysql.cj.jdbc.ConnectionImpl@1e957e2f] will not be managed by Spring
==> Preparing: SELECT id,name,age,email FROM user
==> Parameters:
<== Columns: id, name, age, email
<== Row: 1, Jone, 18, test1@baomidou.com
<== Row: 2, Jack, 20, test2@baomidou.com
<== Row: 3, Tom, 28, test3@baomidou.com
<== Row: 4, Sandy, 21, test4@baomidou.com
<== Row: 5, Billie, 24, test5@baomidou.com
<== Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@244418a]
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)

配置完日志之后,后面的学习就需要注意这个自动生成的SQL

CRUD扩展

插入操作

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testInsert() {
User user = new User();
user.setName("小周");
user.setAge(18);
user.setEmail("123@163.com");

int insert = userMapper.insert(user);
System.out.println(user);
System.out.println(insert);
}
1
2
3
4
5
JDBC Connection [HikariProxyConnection@1652552120 wrapping com.mysql.cj.jdbc.ConnectionImpl@62b57479] will not be managed by Spring
==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
==> Parameters: 1811676431656267777(Long), 小周(String), 18(Integer), 123@163.com(String)
<== Updates: 1
User(id=1811676431656267777, name=小周, age=18, email=123@163.com)

虽然我们没有设置id的值 但是输出可以看到 已经有id值 所以他是会自动生成的

数据库插入的id的默认值为:全局的唯一id

主键生成策略

雪花算法:

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit是机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号位,永远是0。

主键自增:

我们需要配置主键自增

  • 实体类字段上 @TableID(type = IdType.AUTO)
  • 数据库字段一定要设置为自增

其余源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public enum IdType {
AUTO(0),
NONE(1),
INPUT(2),
ASSIGN_ID(3),
ASSIGN_UUID(4);

private final int key;

private IdType(int key) {
this.key = key;
}

public int getKey() {
return this.key;
}
}

更新操作

1
2
3
4
5
6
7
8
9
@Test
public void testUpdate() {
User user = new User();
user.setId(5L);
user.setName("hhhh");

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

自动填充

创建时间、修改时间!这些个操作一遍都是自动化完成的,我们不希望手动更新!

阿里巴巴开发手册:所有的数据库表:gmt_create、gmt_modified几乎所有的表都要配置上,而且需要自动化

方式一:数据库级别(工作中不允许)

1、在表中新增字段 create_time、update_time,选择上根据当前时间戳更新

方式二:代码级别

1、删除数据库的默认值、更新操作

2、实体类字段属性上需要

1
2
3
4
5
6
7
8
9
10
11
12
@Data
public class User {
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;
}

3、编写处理器来处理这个注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

@Override
public void insertFill(MetaObject metaObject) {
log.info("开始插入填充...");
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}

@Override
public void updateFill(MetaObject metaObject) {
log.info("开始更新填充...");
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}

乐观锁

在面试过程中,我们经常会被问到乐观锁,悲观锁

乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么都不去上锁。如果出现了问题,再次更新值测试

悲观锁:顾名思义十分悲观,他总是认为会出现问题,无论干什么都去上锁,再去操作

这里主要讲乐观锁

乐观锁的实现通常包括以下步骤:

  1. 读取记录时,获取当前的版本号(version)。
  2. 在更新记录时,将这个版本号一同传递。
  3. 执行更新操作时,设置 version = newVersion 的条件为 version = oldVersion
  4. 如果版本号不匹配,则更新失败。
1
2
3
4
5
6
7
-- A
update user set name = "zzk", version = version + 1
where id = 2 and version = 1

--B 线程抢先完成,这个时候version = 2 会导致A线程修改失败
update user set name = "zzk", version = version + 1
where id = 2 and version = 1

测试MP的乐观锁插件

1、数据库增加字段version

2、实体类加对应字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
public class User {
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 // 乐观锁Version注解
private Integer version;
}

3、注册组件

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@MapperScan("com.zhou.mybatis_plus.mapper")
public class MyBatisPlusConfig {

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}

4、测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testOptimisticLocker2() {
//线程1
User user = userMapper.selectById(1L);
user.setName("zzk1");
user.setEmail("1@qq.com");

//线程2
User user2 = userMapper.selectById(1L);
user2.setName("zzk2");
user2.setEmail("2@qq.com");
userMapper.updateById(user2);

userMapper.updateById(user); //如果没有乐观锁就会覆盖插队线程的值
}

查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 批量查询
@Test
public void testSelectByBatchId() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}

//条件查询
@Test
public void testSelectByBatchIds() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "小周");

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

分页查询

分页在网站使用的十分多

1、原始的使用limit进行分页

2、pageHelper第三方插件

3、MP也内置了分页插件

如何使用?

1、配置插件

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@MapperScan("com.zhou.mybatis_plus.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
// 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
return interceptor;
}
}

2、测试

1
2
3
4
5
6
7
8
9
@Test
public void testSelectByPage() {
// 参数一:当前页
// 参数二:页面大小
Page<User> page = new Page<>(1, 5);
userMapper.selectPage(page, null);

page.getRecords().forEach(System.out::println);
}

逻辑删除

物理删除:从数据库中直接移除

逻辑删除:在数据库中没有被移除 ,而是通过一个变量来让他失效,deleted=0 -> deleted=1

管理员可以查看被删除的记录,防止数据的丢失,类似回收站

1、在数据表中增加deleted字段

2、实体类中增加属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
public class User {
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 // 乐观锁Version注解
private Integer version;

@TableLogic
private Integer deleted;
}

3、配置字段

在配置文件中配置

1
2
3
mybatis-plus.global-config.db-config.logic-delete-field=deleted
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

4、测试

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

会发现并不是真的删除了 而是将数据库中deleted字段变成了1 走的是更新操作 不是删除操作

性能分析插件

我们在平时的开发中,会遇到一些慢sql

MP提供性能分析插件,如果超过这个时间就停止运行

作用:性能分析拦截器,用于输出每条SQL语句及其执行时间

使用的是p6spy 可以在官网上看https://baomidou.com/guides/p6spy/

条件构造器

十分重要:Wrapper

我们写一些复杂的SQL就可以用它来实现

一些示例:

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

@SpringBootTest
public class WrapperTest {
@Autowired
private UserMapper userMapper;

@Test
void contextLoads() {
// 查询name不为空的用户,并且邮箱不为空,年龄大于等于12
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name")
.isNotNull("email")
.ge("age", 12);
userMapper.selectList(wrapper).forEach(System.out::println);
}

@Test
void test2() {
// 查询名字 小周
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "小周");
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}

@Test
void test3() {
// 查询年龄20-30之间的用户的数量
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 20, 30);
Long count = userMapper.selectCount(wrapper);
System.out.println(count);
}

@Test
void test4() {
// 模糊查询
// 名字里面不包含e 且邮箱为t%
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.notLike("name", "e").likeRight("email", "t");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}

@Test
void test5() {
// 模糊查询
// 名字里面不包含e 且邮箱为t%
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id", "select id from user where id<3");
List<Object> objects = userMapper.selectObjs(wrapper);

}
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!