目录
前言
1. MybatisPlus完全支持Mybatis的功能,只是作为Mybatis的补充而非替代
2. MybatisPlus的优势是在单表查询,可以极大的简化代码逻辑的书写,利用提供的方法直接完成
3. Lambda表达式特别好用
4. 润物细无声,单表效率至上
一、快速入门
1.1 入门案例 & 使用步骤说明
1. 引入Mybatis起步依赖替代Mybatis依赖
<!--MybatisPlus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
2. 定义Mapper接口继承BaseMapper【继承之后才能使用MP相关的方法】
public interface UserMapper extends BaseMapper<User> {
// 有必要讲一下:BaseMapper<User>中的User是使用MP对应的实体类。
// MP通过你指定好的实体类进行扫描获取相应的属性,才知道对应的方法要怎么去完成
}
3. 入门案例展示:
新增用户功能
根据id查询用户
根据id批量查询用户
根据id更新用户
根据id删除用户
userMapper.insert(user);
User user = userMapper.selectById(5L);
List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L));
userMapper.updateById(user);
userMapper.deleteById(5L);
1.2 常见注解
mp映射的实体类的规范:
- 类名驼峰转下划线作为表名
- 名为id的字段作为主键
- 变量名驼峰转下划线作为表的字段名
mp常见的作用于实体类对象上的注解:
- @TableName:指定表名
- @TableId:指定表中的主键字段
- @TableFieId:指定表中的普通字段
【注意】
使用@TableField的常见场景:
- 成员变量名与数据库字段名不一致 例如 username 和 name
- 成员变量名以is开头,且是布尔值 例如 is_married 和 isMarried
- 成员变量名与数据库关键字冲突 例如 order
- 成员变量不是数据库字段
IdType的常见类型:AUTO(自增)、ASSIGN_ID(雪花算法)、INPUT(set输入)
1.3 常见配置
MyBatisPlus中的配置大都是默认配置好的,我们使用的时候基本不用修改大量的配置,除非遇到特殊的情况需要设置一些配置,可以参考MyBatisPlus的官方文档进行修改。也就是约定大于配置。
二、核心功能
2.1 条件构造器
【扩展阅读推荐】条件构造器AbstractWrapper详解-CSDN博客
【是什么、为什么】在简单CRUD操作中,使用MP BaseMapper中的泛型方法可以从容应对。然而对于复杂的需求是,往往需要携带各种条件参数进行CRUD,MybatisPlus的条件构造器就是用于编写复杂的SQL语句。
观察 BaseMapper,除了泛型参数外,还有一个Wrapper参数,专门是用于传递参数条件的。
对于Wrapper类进行说明:
展开看AbstractWrapper抽象基类:
展开看QueryWrapper子类:
扩展看UodateWrapper子类:
2.1.2 基于QueryWrapper的条件查询 & Lambda扩展优化
需求: 查询出名字中带o的,存款大于等于1000元的人的id、username、info、balance字段
2.1.2 基于UpdateWrapper的条件查询 & Lambda扩展优化
需求:更新id为1,2,4的用户的余额,扣200
2.1.3 总结
1. QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分
2. UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用
3. 尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码问题
2.2 自定义SQL
为什么要用自定义SQL,结合XML + MP的相关优势。对于简单部分,我们可以直接用Mybatis提供的部分,而对于复杂的条件部分,如果还使用XML来做,代码就显得十分冗杂。因此在where部分,我们可以通过MP去提供。从而形成相对简单的SQL语句书写方式。
【基本步骤】
1. 基于Wrapper构建where条件
2. 在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew【底层定义好的】
3. 自定义SQL,并使用Wrapper条件
2.3 MP的Service接口
前面介绍的MP方法都是作用在Mapper层的,而我们的业务代码逻辑通常会涉及到Service层上。于是MP在Service层上也实现了一个封装若干方法的IService的接口,用于实现基本的业务逻辑。
2.3.1 实现Service接口的基本流程
- 首先需要你的Service继承MP实现的IService接口
- 其次为了在实现类中简化实现,需要你的ServiceImpl实现Service接口的同时,继承MP中的ServiceImpl。
2.3.2 Service接口方法的使用
简单方法直接调用方法解决,复杂方法才需要在业务层进行拆分
@RestController
@RequestMapping("/user")
public class UserComtroller {
@Resource
private UserService userService;
/**
* 新增用户
*/
@PostMapping()
@ApiOperation(value = "新增用户")
public void addUser(@RequestBody UserFormDTO userDTO) {
// 1. DTO转PO
User user = BeanUtil.copyProperties(userDTO, User.class);
//2. 调用新增方法
userService.save(user);
}
/**
* 删除用户
*/
@DeleteMapping("{id}")
public void deleteUser(@PathVariable( "id") Long id) {
userService.removeById(id);
}
/**
* 根据io查询用户
*/
@GetMapping("{id")
public UserVO getUserById (@PathVariable("id") Long id) {
User u = userService.getById(id);
// PO转VO
UserVO userVO = BeanUtil.copyProperties(u, UserVO.class);
return userVO;
}
/**
* 根据id批量查询用户
*/
@GetMapping()
public List<UserVO> getUserListByIds(@RequestParam List<Long> ids) {
List<User> users = userService.listByIds(ids);
// PO转VO
List<UserVO> userVOS = BeanUtil.copyToList(users, UserVO.class);
return userVOS;
}
/**
* 根据id扣减余额
*
*/
@PutMapping("/{id}/deduction/{money}")
public void deductionMoney(@PathParam("id") Long id, @PathParam("money") Integer money) {
userService.deductionMoney(id, money);
}
}
/*--------------------ServiceImpl-------------------------*/
/**
* 扣减金额
* @param id
* @param money
*/
@Override
public void deductionMoney(Long id, Integer money) {
// 首先思考Service接口有没有方法,有就直接调自己
// 1. 根据id查询用户
User user = getById(id);
if(user == null){
throw new RuntimeException("用户不存在");
}
//2. 判断用户余额是否足够
if(user.getBalance() < money){
throw new RuntimeException("余额不足");
}
//3. 执行扣减语句
user.setBalance(user.getBalance() - money);
updateById(user);
}
2.3.3 Service接口的Lambda查询使用
/**
* 复杂条件查询 使用Lambda
* @param userQDTO
*/
@Override
public List<UserVO> getUserList(UserQueryDTO userQDTO) {
String name = userQDTO.getUserName();
Integer status = userQDTO.getStatus();
Integer minBalance = userQDTO.getMinBalance();
Integer maxBalance = userQDTO.getMaxBalance();
List<User> userList = lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list();
List<UserVO> userVOS = BeanUtil.copyToList(userList, UserVO.class);
return userVOS;
}
2.3.4 Service接口的Lambad修改使用
需求:
改造根据id修改用户余额的接口,要求如下
- 完成对用户状态校验
- 完成对用户余额校验
- 如果扣减后余额为0,则将用户status修改为冻结状态(2)
注意:这里的乐观锁实际上利用了 CAS法。在进行更新前,检查要更新的金额是不是和查询到的相同,如果相同表明当前金额没有被别人修改,可以放心修改,这就是乐观策略
2.3.5 Service接口的Lambda批量新增使用及批处理方案分析
需求:批量插入10万条用户数据,并作出对比:
1. 普通for循环插入
10万条SQL语句、10万次提交事务请求
2. IService的批量插入
以1000次为批量插入,10万条SQL语句,100次提交事务请求,大大减少网络请求,提高了效率
3. 开启rewriteBatchedStatements=true参数
以1000次为批量插入,重写成100条SQL语句,100次提交事务请求,大大减少网络请求,提高了效率
总结:
三、扩展功能
3.1 代码生成
就是借助工具把基本的架子生成,常见的工具有MybatisX、Mybatis【IDEA插件】
演示Mybatis插件:
3.2 DB静态工具
DB静态工具类也是提供了一系列CRUD的方法,与Service接口不同的是,由于静态工具类方法无法直接获取类的对象,需要我们使用者手动传递对象的字节码。通过反射来获取对象的属性,正如方法中的T泛型。
为什么要用DB静态工具类?
DB的基本语法使用:和Service基本一致
3.3 逻辑删除
逻辑删除就是模拟删除效果,但实际上并不会将数据库中的数据删除掉。对于一些需要进行统计、或者安全性较高的场景下,我们常常需要使用逻辑删除而非物理删除。
常见的逻辑删除策略就是在数据库表中添加一个逻辑字段 deleted(1:删除,0:没删除),用于标记数据是否删除。当用户发送删除请求是,此时不再执行Delete语句,而是执行Update语句。将deleted 置为 1
具体在实现的过程中,我们并不用关系修改语句实现,只需要通过配置MybatisPlus,就能让MP帮我们在底层将逻辑功能删除实现:
3.4 枚举处理器
在比较赋值的时候,当状态多了,单单通过数字1、2、3、4来比较很容易出错。因此我们可以利用枚举类型将比较赋值的方式规范化。从而提高代码的可阅读性和可维护性。
如何使用枚举类型处理器
1. 创建枚举类,给枚举中的与数据库对应value值添加@EnumValue注解
2. 在配置文件中配置统一的枚举处理器,实现类型转换
3. 使用情况:
3.5 JSON处理器
有时候,如果设计的数据库字段值有一个字段是JSON类型的。在后端交付数据给前端的时候,我们往往需要预处理好再交给前端。
我们使用一个UserInfo对象来描述JSON字段。使用MP的JSON处理器就可以实现上述的映射转换:
JSON使用步骤——基于注解
四、插件功能
4.1 分页插件介绍
MP也提供一个分页插件,我觉得实际上就是将PageHelper给实现了一下,底层也是基于拦截器去做的。
1. 首先,要在配置类中注册MyBatisPlus的核心插件,同时添加分页插件:
@Configuration
//MybatisPlus配置类
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1. 初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//2. 添加分页插件 指定数据库类型
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
//3. 设置分页参数
paginationInnerInterceptor.setMaxLimit(1000L);
//4. 将分页插件添加到核心插件中
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
2. 构建分页对象参数,然后使用分页方法就行了
@Test
void testMPPageQuery() {
//1. 构建分页条件
int pageCurrent = 1;
int pageSize = 2;
Page<User> page = Page.of(pageCurrent, pageSize);
// 2. 构建查询条件
LambdaQueryWrapper<User> wrapper= new LambdaQueryWrapper<User>()
.like(User::getUsername,"o")
.ge(User::getBalance,1000);
//3. 执行查询,传入page对象
userMapper.selectPage(page,wrapper);
}
五、MybatisPlus追问巩固
1. MyBatisPlus使用的基本流程是什么?
2. MybatisPlus是如何获取实现CRUD的数据库表信息的?
3. MybatisPlus的常用注解有哪些?
4. IdType的常见类型有哪些?
5. 使用@TableField的常见场景是?
6. 什么是条件构造器?为什么要有条件构造器?
7. Wrapper类有哪些常见的子类?分别有哪些特点?
8. LambdaQueryMrapper 和 LambdaUpdateMrapper 特点是是什么?
9. 为什么MP还要有自定义SQL?
10. Service接口和 Mapper接口有何区别?为什么还要有一个继承了MP封装方法的Service接口?
11. 请你概述一下MP的Service接口的基本使用流程【注意事项】
12. 简单谈一下你对批量插入方案的看法,如何提高批量插入的效率?
13. 为什么要有DB静态工具,DB静态工具的使用场景是什么?
14. DB静态工具相对于Service接口的方法特点是什么?
15. 删除策略——介绍一下逻辑删除的必要性和使用场景?
16. 逻辑删除策略的实现方案及其比较?
17. 如何实现PO类中的枚举类型变量与数据库字段的转换?
18. MP中如何实现将JSON类型字段转Java对象?
19.MP分页插件的使用步骤?