MybatisPlus学习笔记

MybatisPlus学习笔记

1. 快速入门

实现步骤:

  • 引入MybatisPlus依赖
  • 定义Mapper

1.1 引入依赖

1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>

1.2 定义Mapper

  • MybatisPlus提供了一个基础的BaseMapper接口
  • 修改mp-demo中的com.itheima.mp.mapper包下的UserMapper接口,让其继承BaseMapper:
1
2
public interface UserMapper extends BaseMapper<User> {
}

2. 常见注解

2.1 默认情况

MybatisPlus就是根据PO实体的信息来推断出表的信息,从而生成SQL的。默认情况下:

  • MybatisPlus会把PO实体的类名驼峰转下划线作为表名
  • MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
  • MybatisPlus会把名为id的字段作为主键

2.2 @TableName

  • 描述:表名注解,标识实体类对应的表
  • 使用位置:实体类

2.3 @TableId

  • 描述:主键注解,标识实体类中的主键字段
  • 使用位置:实体类的主键字段

IdType属性的常见值:

  • AUTO:利用数据库的id自增长
  • INPUT:手动生成id
  • ASSIGN_ID:雪花算法生成Long类型的全局唯一id,这是默认的ID策略

2.4 @TableField

普通字段注解

1
2
3
4
5
6
7
8
9
10
11
@TableName("user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
@TableField(is_married")
private Boolean isMarried;
@TableField("`concat`")
private String concat;
}

添加@TableField注解的一些特殊情况:

  • 成员变量名与数据库字段名不一致
  • 成员变量是以isXXX命名,按照JavaBean的规范,MybatisPlus识别字段时会把is去除,这就导致与数据库不符。
  • 成员变量名与数据库一致,但是与数据库的关键字冲突。使用@TableField注解给字段名添加转义字符:``

3. 核心功能

3.1 条件构造器

除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外,还支持更加复杂的where条件。

Wrapper就是条件构造的抽象类

3.1.1 QueryWrapper

查询:查询出名字中带o的,存款大于等于1000元的人。代码如下:

1
2
3
4
5
6
7
8
9
10
11
@Test
void testQueryWrapper() {
// 1.构建查询条件 where name like "%o%" AND balance >= 1000
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username", "info", "balance")
.like("username", "o")
.ge("balance", 1000);
// 2.查询数据
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}

3.1.2 UpdateWrapper

1
2
3
4
5
6
7
8
9
10
11
@Test
void testUpdateWrapper() {
List<Long> ids = List.of(1L, 2L, 4L);
// 1.生成SQL
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200") // SET balance = balance - 200
.in("id", ids); // WHERE id in (1, 2, 4)
// 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据,
// 而是基于UpdateWrapper中的setSQL来更新
userMapper.update(null, wrapper);
}

3.1.3 LambdaQueryWrapper

1
2
3
4
5
6
7
8
9
10
11
12
@Test
void testLambdaQueryWrapper() {
// 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
// 2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}

3.2 自定义SQL

1
2
3
4
5
6
7
8
9
@Test
void testCustomWrapper() {
// 1.准备自定义查询条件
List<Long> ids = List.of(1L, 2L, 4L);
QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);

// 2.调用mapper的自定义方法,直接传递Wrapper
userMapper.deductBalanceByIds(200, wrapper);
}

然后在UserMapper中自定义SQL:

1
2
3
4
public interface UserMapper extends BaseMapper<User> {
@Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")
void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}

3.3 Service接口

3.3.1 介绍

MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。
通用接口为IService默认实现为ServiceImpl,其中封装的方法可以分为以下几类:

  • save:新增
  • remove:删除
  • update:更新
  • get:查询单个结果
  • list:查询集合结果
  • count:计数
  • page:分页查询

3.3.2 基本用法

自定义Service接口继承IService以拓展方法。同时,让自定义的Service实现类继承ServiceImpl

1
2
3
public interface IUserService extends IService<User> {
// 拓展自定义方法
}

编写UserServiceImpl类,继承ServiceImpl,实现UserService

1
2
3
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}

3.3.3 Lambda

IService中还提供了Lambda功能来简化我们的复杂查询及更新功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
@Transactional
public void deductBalance(Long id, Integer money) {
// 1.查询用户
User user = getById(id);
// 2.校验用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
// 3.校验余额是否充足
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足!");
}
// 4.扣减余额 update tb_user set balance = balance - ?
int remainBalance = user.getBalance() - money;
lambdaUpdate()
.set(User::getBalance, remainBalance) // 更新余额
.set(remainBalance == 0, User::getStatus, 2) // 动态判断,是否更新status
.eq(User::getId, id)
.eq(User::getBalance, user.getBalance()) // 乐观锁
.update();
}

4. 扩展功能

4.1 代码生成

使用MybatisX插件即可

SpringBoot中MybatisX插件的简单使用教程(超详细!!)

4.2 静态工具

有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法与IService中方法签名基本一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public UserVO queryUserAndAddressById(Long userId) {
// 1.查询用户
User user = getById(userId);
if (user == null) {
return null;
}
// 2.查询收货地址
List<Address> addresses = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, userId)
.list();
// 3.处理vo
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
return userVO;
}

在查询地址时,我们采用了Db的静态方法,因此避免了注入AddressService,减少了循环依赖的风险。

4.3 逻辑删除

对于一些比较重要的数据,我们不删除数据库中的数据,而是

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为true

我们要在application.yml中配置逻辑删除字段

1
2
3
4
5
6
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.4 枚举处理器

MybatisPlus提供了一个处理枚举的类型转换器,可以帮我们把枚举类型与数据库类型自动转换

4.4.1 定义枚举

4.4.2 @EnumValue

MybatisPlus提供了@EnumValue注解来标记枚举属性

1
2
@EnumValue
private final int value;

表示value字段的值是数据库值

4.4.3 配置枚举处理器

在application.yaml文件中添加配置

1
2
3
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

4.5 JSON类型处理器

数据库的user表中有一个info字段,是JSON类型。而目前User实体类中却是String类型。处理JSON就可以使用JacksonTypeHandler处理器

使用类型处理器
将User类的info字段修改为UserInfo类型,并声明类型处理器:

1
2
@TableField(typeHandler = JacksonTypeHandler.class)
private UserInfo info;

5. 插件功能(以分页插件为例)

MybatisPlus提供了很多的插件功能,进一步拓展其功能。

  • PaginationInnerInterceptor:自动分页
  • TenantLineInnerInterceptor:多租户
  • DynamicTableNameInnerInterceptor:动态表名
  • OptimisticLockerInnerInterceptor:乐观锁
  • IllegalSQLInnerInterceptor:sql 性能规范
  • BlockAttackInnerInterceptor:防止全表更新与删除

5.1 配置分页插件

在项目中新建一个配置类:MybatisConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisConfig {

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

5.2 分页查询的测试

1
2
3
4
5
6
7
8
9
10
11
12
@Test
void testPageQuery() {
// 1.分页查询,new Page()的两个参数分别是:页码、每页大小
Page<User> p = userService.page(new Page<>(2, 2));
// 2.总条数
System.out.println("total = " + p.getTotal());
// 3.总页数
System.out.println("pages = " + p.getPages());
// 4.数据
List<User> records = p.getRecords();
records.forEach(System.out::println);
}

END