MyBatis-Plus
一、介绍
MyBatis-Plus(简称 MP)是一个基于 MyBatis 的增强工具,它简化了 MyBatis 的开发过程,提供了许多自动化的功能,减少了开发者的工作量。MyBatis-Plus 主要提供了对 MyBatis 的扩展,增加了很多功能,例如自动生成 SQL、分页查询、性能分析等。
主要特点:
1.简化 MyBatis 的操作:
- 单表操作:MyBatis-Plus 提供了通用的 BaseMapper,可以大大简化增删改查操作。例如,使用 BaseMapper 提供的默认方法,可以直接进行插入、删除、更新和查询等操作,而不需要自己写 SQL。
- 减少冗余代码:只需要简单地继承 BaseMapper 接口,就可以完成对数据库表的操作,避免了手写大量的 CRUD 代码。
2.自动生成 SQL:
- 条件构造器:MyBatis-Plus 提供了丰富的 Wrapper类(如 QueryWrapper和 UpdateWrapper),可以通过构造器模式灵活地构建 SQL 查询和更新条件,避免手写 SQL 语句。
- SQL 注入器:MyBatis-Plus 采用了 SQL 注入器机制,使得开发者可以在不写 SQL 的情况下,完成对数据库的常规操作。
3.分页功能:
- MyBatis-Plus 集成了分页插件,支持基于分页参数的 SQL 查询,无需额外配置,只需要简单使用。
4.性能分析:
- MyBatis-Plus 提供了 SQL 性能分析插件,可以帮助开发者分析 SQL 执行的性能,避免不合理的查询。
5.代码生成器:
- MyBatis-Plus 提供了代码生成器,能够自动根据数据库表生成对应的 Java 实体类、Mapper 接口、XML 文件等,减少手动编写代码的工作量。
6.逻辑删除:
- MyBatis-Plus 支持逻辑删除,通过配置字段,可以将记录标记为已删除,而不是物理删除。
7.多种自定义功能:
- 自定义 SQL 方法:MyBatis-Plus 允许开发者根据需要自定义 SQL 操作,提供更大的灵活性。
- 支持全局配置:开发者可以配置全局的规则,比如字段名自动驼峰转化、主键策略等。
主要功能和优势:
1.简化开发:
MyBatis-Plus 抽象了大量重复的操作,让开发者只关注业务逻辑。
2.增强 MyBatis 功能:
MyBatis 本身是一个很强大的持久层框架,而 MyBatis-Plus 通过提供许多增强功能,让 MyBatis 更加易用。
3.提高开发效率:
提供了自动化生成代码、分页查询、条件构造器等功能,可以极大提高开发效率。
4.兼容性强:
MyBatis-Plus 保持与 MyBatis 的高度兼容,可以无缝集成到现有的 MyBatis 项目中。
二、快速使用
1.引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
2.自定义Mapper基础BaseMapper
3.在实体类上添加注解声明 表信息
4.在application.yml中根据需要添加配置
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.itheima.mp.domain.po
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启日志
map-underscore-to-camel-case: true # 开启驼峰命名转换
cache-enabled: false # 禁用二级缓存
global-config:
db-config:
id-type: auto # 主键自增
update-strategy: not_null # 只更新非空字段
5.注入service测试
三、常用注解
1. @TableName
- 用于指定实体类与数据库表的映射关系。
- 如果实体类的名称与数据库表名称不同,可以使用此注解进行映射。
@TableName("tb_user")
public class User {
// 属性...
}
2. @TableId
- 用于标记实体类中的主键字段,通常是与数据库表的主键列对应。
- @TableId支持主键的自增、雪花算法等策略。
@TableId(value = "id", type = IdType.AUTO)
private Long id;
3. @TableField
- 用于指定实体类字段与数据库表字段的映射关系。
- 可以指定一些额外的映射特性,如是否插入、是否更新等。
@TableField(value = "user_name", exist = true)
private String userName;
4. @Version
- 用于标记乐观锁字段。
- 通过该字段进行并发控制,防止多个线程同时修改同一条记录导致的数据冲突。
@Version
private Integer version;
5. @TableLogic
- 用于标记逻辑删除字段,MyBatis-Plus 会自动根据该字段的值来处理删除操作。
- 通常该字段为
deleted,值为1表示已删除,0表示未删除。
@TableLogic
private Integer deleted;
6. @TableField(exist = false)
- 用于表示该字段不是数据库表中的字段,避免 MyBatis-Plus 尝试映射到数据库表。
- 例如,某些属性是计算出来的字段,或者不希望进行数据库操作的字段。
@TableField(exist = false)
private String tempData;
这些注解是 MyBatis-Plus 提供的一些常见注解,通过这些注解,可以简化实体类的定义和数据库操作的配置。
四、常用配置
1. 基本配置
mybatis-plus:
global-config:
db-config:
# 设置数据库类型
database-type: MYSQL
# 主键策略
id-type: auto
# 逻辑删除的字段值
logic-delete-value: 1
logic-not-delete-value: 0
# 是否开启AR模式(ActiveRecord模式)
active-record: false
# 字段策略,未配置则采用默认
field-strategy: not_empty
# 是否开启全局缓存
enable-cache: false
2. 分页插件配置
MyBatis-Plus 内置了分页插件,可以在 application.yaml 中配置分页插件的相关参数。
mybatis-plus:
configuration:
# 开启分页插件
pagination-interceptor:
# 设置最大查询条数
limit: 500
# 是否允许查询超出最大页码的数据,默认false
overflow: false
3. SQL 注入配置
MyBatis-Plus 可以根据不同的数据库类型自动配置适配器。通过 database-type 可以指定具体的数据库类型。
mybatis-plus:
global-config:
db-config:
# 设置数据库类型(MySQL、PostgreSQL、Oracle 等)
database-type: MYSQL
sql-injector: com.baomidou.mybatisplus.core.injector.DefaultSqlInjector
4. 自动填充配置
通过 MyBatis-Plus 的自动填充功能,可以配置全局的填充策略,例如创建时间和更新时间等字段的自动填充。
mybatis-plus:
global-config:
db-config:
# 设置自动填充字段(比如插入时填充创建时间,更新时填充更新时间)
fill-strategy: insert,update
5. 多数据源配置
如果项目中有多个数据源,可以在 application.yaml 中配置多数据源,并通过 @Primary 或 @Qualifier 注解进行切换。
spring:
datasource:
dynamic:
primary: master
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db
username: root
password: password
slave:
url: jdbc:mysql://localhost:3306/slave_db
username: root
password: password
6. 日志打印配置
MyBatis-Plus 可以配置 SQL 执行日志的打印。通过设置日志级别为 debug 可以打印出执行的 SQL 语句。
logging:
level:
com.baomidou.mybatisplus: DEBUG
7. 表前缀和字段命名策略
可以配置全局表前缀和字段命名策略,以便统一处理数据库表和实体类之间的映射关系。
mybatis-plus:
global-config:
db-config:
# 表前缀
table-prefix: mp_
# 字段命名策略
field-strategy: not_empty
# 表字段驼峰命名规则
name-style: camel
8. SQL 注入器和自定义 SQL 策略
MyBatis-Plus 支持自定义 SQL 注入器,用来实现更复杂的 SQL 生成逻辑。可以在 application.yaml 中配置。
mybatis-plus:
global-config:
sql-injector:
# 可以设置为自定义的 SQL 注入器
custom-sql-injector: com.example.CustomSqlInjector
9. MyBatis 配置
如果需要对 MyBatis 的其他配置项进行调整(例如缓存、延迟加载等),可以在 application.yaml 中进行配置。
mybatis-plus:
configuration:
# 是否启用缓存
cache-enabled: true
# 是否启用懒加载
lazy-load-enabled: true
10. 其他常用配置
- 实体类扫描路径:
mybatis-plus:
type-aliases-package: com.example.domain
- Mapper.xml 文件扫描路径:
mybatis-plus:
mapper-locations: classpath:mapper/**/*Mapper.xml
五、核心功能
1.条件构造器
基于QueryWrapper的查询
@Test
void testQueryWrapper() {
//需求:查询出名字中带a的,存款大于等于1000元的人的id、username、info、balance字段
QueryWrapper<User> userQueryWrapper = new QueryWrapper<User>()
.select("id","username","info","balance")
.like("username","a")
.ge("balance",1000);
userMapper.selectList(userQueryWrapper).forEach(System.out::println);
}
基于QueryWrapper的更新
@Test
void testUpdateQueryWrapper() {
//需求:将用户名为jack的薪资调整为100000000
QueryWrapper<User> queryWrapper = new QueryWrapper<User>()
.eq("username", "jack");
User user = new User();
user.setBalance(100000000);
userMapper.update(user, queryWrapper);
}
基于UpdateWrapper的更新
@Test
void testUpdateWrapper() {
//需求:更新id为1,2,4的用户的余额,加100
List<Long> idList = List.of(1L, 2L, 4L);
UpdateWrapper<User> updateWrapper = new UpdateWrapper<User>()
.setSql("balance = balance + 100")
.in("id", idList);
userMapper.update(null, updateWrapper);
}
使用LamdaWrapper
@Test
void testLambdaQueryWrapper() {
//可以使用QueryWrapper.lambda()方式
LambdaQueryWrapper<User> lambdaQueryWrapper = new QueryWrapper<User>().lambda()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.eq(User::getUsername, "jack");
//也可以直接使用LambdaQueryWrapper方式
LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<User>()
.eq(User::getUsername, "jack");
userMapper.selectList(lambdaQueryWrapper).forEach(System.out::println);
userMapper.selectList(userLambdaQueryWrapper).forEach(System.out::println);
}
总结
条件构造器的用法:
•QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分
•UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用
•尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码
2.自定义SQL
我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。
①基于Wrapper构建where条件
List<Long> ids = List.of(1L, 2L, 4L);
int amount = 200;
// 1.构建条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, ids);
// 2.自定义SQL方法调用
userMapper.updateBalanceByIds(wrapper, amount);
②在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew
void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);
③自定义SQL,并使用Wrapper条件
<update id="updateBalanceByIds">
UPDATE tb_user SET balance = balance - #{amount} ${ew.customSqlSegment}
</update>
代码示例
@Test
void testCustomSqlUpdate() {
//1.更新条件
List<Long> ids = List.of(1L, 3L, 5L);
int amount = 200;
//2.定义条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.in(User::getId, ids);
//3.调用自定义方法
userMapper.updateBalanceByIds(wrapper,amount);
}
//可以直接定义参数名为“ew”
// void updateBalanceByIds(@Param("ew")LambdaQueryWrapper<User> wrapper,@Param("amount") int amount);
//也可以使用Constants.WRAPPER
void updateBalanceByIds(@Param(Constants.WRAPPER)LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);
<!-- where条件由ew.customSqlSegment表达式生成-->
<update id="updateBalanceByIds">
Update tb_user set balance = balance + #{amount} ${ew.customSqlSegment}
</update>
MP的Service接口使用流程
自定义Service接口继承IService接口
public interface IUserService extends IService<User> {}
自定义Service实现类,实现自定义接口并继承ServiceImpl类
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {}
3.Service接口
定义IUserService,继承IService
package com.itheima.mp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/01/16 22:40
*/
public interface IUserService extends IService<User> {
}
UserServiceImpl类,需要继承ServiceImpl,实现UserService:
package com.itheima.mp.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.mapper.UserMapper;
import com.itheima.mp.service.IUserService;
import org.springframework.stereotype.Service;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/01/16 22:41
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
最后进行测试:
package com.itheima.mp.service;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
@SpringBootTest
class IUserServiceTest {
@Autowired
private UserServiceImpl userService;
@Test
void testInsert() {
User user = new User();
user.setId(7L);
user.setUsername("李四");
user.setPassword("123");
user.setPhone("77777777");
user.setBalance(200000);
user.setInfo("{"age": 24, "intro": "辅导员", "gender": "female"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userService.save(user);
}
}
4.案例:基于Restful风格实现接口
pom依赖:
<!--swagger-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
配置swagger信息:
knife4j:
enable: true
openapi:
title: 用户管理接口文档
description: "用户管理接口文档"
email: 1002411753@qq.com
concat: Prode
url: https://www.itcast.cn
version: v1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources:
- com.itheima.mp.controller
接口需要两个实体:
- UserFormDTO:代表新增时的用户表单
- UserVO:代表查询的返回结果
package com.itheima.mp.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/01/17 10:14
*/
@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("注册手机号")
private String phone;
@ApiModelProperty("详细信息,JSON风格")
private String info;
@ApiModelProperty("账户余额")
private Integer balance;
}
package com.itheima.mp.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/01/17 10:16
*/
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
@ApiModelProperty("用户id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("详细信息")
private String info;
@ApiModelProperty("使用状态(1正常 2冻结)")
private Integer status;
@ApiModelProperty("账户余额")
private Integer balance;
}
Controller:
package com.itheima.mp.controller;
import cn.hutool.core.bean.BeanUtil;
import com.itheima.mp.domain.dto.UserFormDTO;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;
import com.itheima.mp.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/01/17 10:17
*/
@Api(tags = "用户管理接口")
@RequiredArgsConstructor
@RestController
@RequestMapping("users")
public class UserController {
//final和@RequiredArgsConstructor注解搭配使用
private final IUserService userService;
@PostMapping
@ApiOperation("新增用户")
public void saveUser(@RequestBody UserFormDTO userFormDTO){
// 1.转换DTO为PO
User user = BeanUtil.copyProperties(userFormDTO, User.class);
// 2.新增
userService.save(user);
}
@DeleteMapping("/{id}")
@ApiOperation("删除用户")
public void removeUserById(@ApiParam("用户id") @PathVariable("id") Long userId){
userService.removeById(userId);
}
@GetMapping("/{id}")
@ApiOperation("根据id查询用户")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long userId){
// 1.查询用户
User user = userService.getById(userId);
// 2.处理vo
return BeanUtil.copyProperties(user, UserVO.class);
}
@GetMapping
@ApiOperation("根据id集合查询用户")
public List<UserVO> queryUserByIds(@ApiParam("用户id的List集合") @RequestParam("ids") List<Long> ids){
// 1.查询用户
List<User> users = userService.listByIds(ids);
// 2.处理vo
return BeanUtil.copyToList(users, UserVO.class);
}
@PutMapping("{id}/deduction/{money}")
@ApiOperation("扣减用户余额")
public void deductBalance(@PathVariable("id") Long id, @PathVariable("money")Integer money){
userService.deductBalance(id, money);
}
}
UserService:
package com.itheima.mp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/01/16 22:40
*/
public interface IUserService extends IService<User> {
void deductBalance(Long id, Integer money);
}
UserServiceImpl:
package com.itheima.mp.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.mapper.UserMapper;
import com.itheima.mp.service.IUserService;
import org.springframework.stereotype.Service;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/01/16 22:41
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public void deductBalance(Long id, Integer money) {
// 1.根据id查询用户
User user = getById(id);
// 2.判断用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常");
}
// 3.判断用户余额
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足");
}
// 4.扣减余额
baseMapper.deductMoneyById(id, money);
}
}
mapper:
@Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")
void deductMoneyById(@Param("id") Long id, @Param("money") Integer money);
5.Iservice基于Lambda的查询
定义查询条件实体
package com.itheima.mp.domain.query;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/02/25 21:21
*/
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}
Controller:
@ApiOperation("根据复杂条件查询用户接口")
@GetMapping("/list")
public List<UserVO> queryUsers(UserQuery query){
// 1.组织条件
String username = query.getName();
Integer status = query.getStatus();
Integer minBalance = query.getMinBalance();
Integer maxBalance = query.getMaxBalance();
// LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda()
// .like(username != null, User::getUsername, username)
// .eq(status != null, User::getStatus, status)
// .ge(minBalance != null, User::getBalance, minBalance)
// .le(maxBalance != null, User::getBalance, maxBalance);
// 2.查询用户
List<User> users = userService.lambdaQuery()
.like(username != null, User::getUsername, username)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list();
// 3.处理vo
return BeanUtil.copyToList(users, UserVO.class);
}
可以发现lambdaQuery方法中除了可以构建条件,还需要在链式编程的最后添加一个list(),这是在告诉MP我们的调用结果需要是一个list集合。这里不仅可以用list(),可选的方法有:
.one():最多1个结果.list():返回集合结果.count():返回计数结果
MybatisPlus会根据链式编程的最后一个方法来判断最终的返回结果。
6.Iservice基于Lambda的更新
@Override
@Transactional
public void deductBalance(Long id, Integer money) {
// 1.根据id查询用户
User user = getById(id);
// 2.判断用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常");
}
// 3.判断用户余额
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足");
}
// // 4.扣减余额
// baseMapper.deductMoneyById(id, money);
// 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();
}
7.批量新增
首先在application.yaml文件中修改MySQL配置,加入rewriteBatchedStatements=true
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
使用MyBatis-Plus的批处理
@Test
void testSaveBatch() {
// 准备10万条数据
List<User> list = new ArrayList<>(1000);
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
list.add(buildUser(i));
// 每1000条批量插入一次
if (i % 1000 == 0) {
userService.saveBatch(list);
list.clear();
}
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
六、扩展功能
1.代码生成
下载MyBatisPlus插件
点击other->Config Database,然后输入对应的数据库信息
点击other->Code Generator 进行代码生成
最终结果,生成的一些代码没有导入jar包,还需要手动进行处理
2.静态工具
有时不同service直接会互相调用,进而产生循环依赖。比如用户service当中可能注入地址service,而地址service当中也需要注入用户service。这时候就可以直接使用Db来进行。
以下是几个案例:
案例1:
AddressVO:
package com.itheima.mp.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/02/26 22:53
*/
@Data
@ApiModel(description = "收货地址VO")
public class AddressVO{
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("用户ID")
private Long userId;
@ApiModelProperty("省")
private String province;
@ApiModelProperty("市")
private String city;
@ApiModelProperty("县/区")
private String town;
@ApiModelProperty("手机")
private String mobile;
@ApiModelProperty("详细地址")
private String street;
@ApiModelProperty("联系人")
private String contact;
@ApiModelProperty("是否是默认 1默认 0否")
private Boolean isDefault;
@ApiModelProperty("备注")
private String notes;
}
在UserVO当中加入收获地址列表:
package com.itheima.mp.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/01/17 10:16
*/
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
@ApiModelProperty("用户id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("详细信息")
private String info;
@ApiModelProperty("使用状态(1正常 2冻结)")
private Integer status;
@ApiModelProperty("账户余额")
private Integer balance;
@ApiModelProperty("收获地址列表")
private List<AddressVO> address;
}
修改UserController中根据id查询用户的业务接口:
@GetMapping("/{id}")
@ApiOperation("根据id查询用户")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long userId){
// // 1.查询用户
// User user = userService.getById(userId);
// // 2.处理vo
// return BeanUtil.copyProperties(user, UserVO.class);
return userService.queryUserAndAddressById(userId);
}
在IUserService中定义抽象方法:
package com.itheima.mp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/01/16 22:40
*/
public interface IUserService extends IService<User> {
void deductBalance(Long id, Integer money);
UserVO queryUserAndAddressById(Long userId);
}
实现该方法:
@Override
public UserVO queryUserAndAddressById(Long userId) {
// 1.查询用户
User user = getById(userId);
if (user == null) {
return null;
}
// 2.查询收货地址
//这里使用Db静态工具,而不是使用addressService,减少循环依赖风险
List<Address> address = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, userId)
.list();
// 3.处理vo
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
userVO.setAddress(BeanUtil.copyToList(address, AddressVO.class));
return userVO;
}
案例2:
这里只写service实现的部分了:
@Override
public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
// 1.查询用户
List<User> users = listByIds(ids);
if (users == null || users.isEmpty()) {
return Collections.emptyList();
}
// 2查询收货地址
//2.1获取用户id集合列表
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
//2.2根据用户id查询地址
List<Address> addresses = Db.lambdaQuery(Address.class)
.in(Address::getUserId, userIds)
.list();
//2.3转换地址vo
List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);
//2.4用户地址vo处理,将所有用户进行分组,然后把这些用户的地址放到相应的用户对象中
Map<Long, List<AddressVO>> addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
if (CollUtil.isNotEmpty(addressVOList)) {
addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
}
//3.转换vo返回
ArrayList<UserVO> list = new ArrayList<>(users.size());
for (User user : users) {
//3.1转换user的po为vo
UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
list.add(vo);
//3.2转换地址vo
vo.setAddress(addressMap.get(vo.getId()));
}
return list;
}
3.逻辑删除
在application.yaml中配置:
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
4.枚举处理器
在application.yaml中配置:
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.itheima.mp.domain.po
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启日志
map-underscore-to-camel-case: true # 开启驼峰命名转换
cache-enabled: false # 禁用二级缓存
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler # 枚举类型处理器
增加一个枚举类:
package com.itheima.mp.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
/**
* @Description
* @Author lt
* @Data 2025/02/27 12:43
*/
@Getter
public enum UserStatus {
NORMAL(1, "正常"),
FREEZE(2, "冻结")
;
@EnumValue
private final int value;
@JsonValue
private final String desc;
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
修改实体类以及给前端返回的实体类
package com.itheima.mp.domain.vo;
import com.itheima.mp.enums.UserStatus;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/01/17 10:16
*/
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
@ApiModelProperty("用户id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("详细信息")
private String info;
@ApiModelProperty("使用状态(1正常 2冻结)")
private UserStatus status;
@ApiModelProperty("账户余额")
private Integer balance;
@ApiModelProperty("收获地址列表")
private List<AddressVO> address;
}
package com.itheima.mp.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/01/17 10:14
*/
@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("注册手机号")
private String phone;
@ApiModelProperty("详细信息,JSON风格")
private String info;
@ApiModelProperty("账户余额")
private Integer balance;
}
5.JSON处理器
首先需要定义一个单独实体类来与User当中info字段的属性匹配:
package com.itheima.mp.domain.po;
import lombok.Data;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/02/27 17:21
*/
@Data
public class UserInfo {
private Integer age;
private String intro;
private String gender;
}
接下来,将User类的info字段修改为UserInfo类型,并在注解当中声明类型处理器。同时,在User类上添加一个注解,声明自动映射。
package com.itheima.mp.domain.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.itheima.mp.enums.UserStatus;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName(value = "tb_user",autoResultMap = true) // 表名对应数据库表 如果不写默认和数据库名一致
public class User {
/**
* 用户id
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 注册手机号
*/
private String phone;
/**
* 详细信息
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private UserInfo info;
/**
* 使用状态(1正常 2冻结)
*/
private UserStatus status;
/**
* 账户余额
*/
private Integer balance;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
启动报错,(提示:找不到bean对象或者xml文件的问题),需要在UserMapper.xml中指定info的类型typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler
<insert id="saveUser" parameterType="com.itheima.mp.domain.po.User">
INSERT INTO `user` (`id`, `username`, `password`, `phone`, `info`, `balance`)
VALUES
(#{id}, #{username}, #{password}, #{phone}, #{info,typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler}, #{balance});
</insert>
最后进行测试
6.插件功能
6.1分页插件
新建分页配置类
package com.itheima.mp.config;
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;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/02/28 10:51
*/
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
进行测试
@Test
void testPageQuery() {
// 1.分页查询,new Page()的两个参数分别是:页码、每页大小
Page<User> p = userService.page(new Page<>(2, 3));
// 排序参数, 通过OrderItem来指定
p.addOrder(new OrderItem("balance", false));
p.addOrder(new OrderItem("id", true));
// 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);
}
6.2通用分页实体
简单分页查询案例
创建一个分页查询实体,用户查询实体继承分页查询实体
package com.itheima.mp.domain.query;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/02/28 12:30
*/
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
@ApiModelProperty("页码")
private Long pageNo;
@ApiModelProperty("页码")
private Long pageSize;
@ApiModelProperty("排序字段")
private String sortBy;
@ApiModelProperty("是否升序")
private Boolean isAsc;
}
package com.itheima.mp.domain.query;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/02/25 21:21
*/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery{
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}
封装返回实体
package com.itheima.mp.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/02/28 12:33
*/
@Data
@ApiModel(description = "分页结果")
@AllArgsConstructor()
public class PageDTO<T> {
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("总页数")
private Long pages;
@ApiModelProperty("集合")
private List<T> list;
}
controller
@ApiOperation("根据复杂条件查询用户分页接口")
@GetMapping("/page")
public PageDTO<UserVO> queryUsersPage(UserQuery query){
return userService.queryUsersPage(query);
}
Service和ServiceImpl方法
PageDTO<UserVO> queryUsersPage(UserQuery query);
@Override
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
String name = query.getName();
Integer status = query.getStatus();
// 1.构建条件
// 1.1.分页条件
Page<User> page = Page.of(query.getPageNo(), query.getPageSize());
// 1.2.排序条件
if (query.getSortBy() != null) {
page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
}else{
// 默认按照更新时间排序
page.addOrder(new OrderItem("update_time", false));
}
// 2.分页查询
lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.page(page);
// 3.数据非空校验
List<User> records = page.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return new PageDTO<>(page.getTotal(), page.getPages(), Collections.emptyList());
}
// 4.有数据,转换
List<UserVO> list = BeanUtil.copyToList(records, UserVO.class);
// 5.封装返回
return new PageDTO<UserVO>(page.getTotal(), page.getPages(), list);
}
6.3通用分页实体与MP转换(重要)
改造PageQuery实体,在PageQuery实体中定义几个工具方法,简化开发。
package com.itheima.mp.domain.query;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/02/28 12:30
*/
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
@ApiModelProperty("页码")
private Long pageNo;
@ApiModelProperty("页码")
private Long pageSize;
@ApiModelProperty("排序字段")
private String sortBy;
@ApiModelProperty("是否升序")
private Boolean isAsc;
public <T> Page<T> toMpPage(OrderItem ... orders){
// 1.分页条件
Page<T> p = Page.of(pageNo, pageSize);
// 2.排序条件
// 2.1.先看前端有没有传排序字段
if (sortBy != null) {
p.addOrder(new OrderItem(sortBy, isAsc));
return p;
}
// 2.2.再看有没有手动指定排序字段
if(orders != null){
p.addOrder(orders);
}
return p;
}
public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
}
public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
return toMpPage("create_time", false);
}
public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
return toMpPage("update_time", false);
}
}
改造PageDTO实体,提供查询结果转换为DTO的工具类。
package com.itheima.mp.domain.dto;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @Description
* @Author LiTong(Prode)
* @Data 2025/02/28 12:33
*/
@Data
@ApiModel(description = "分页结果")
@AllArgsConstructor()
public class PageDTO<T> {
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("总页数")
private Long pages;
@ApiModelProperty("集合")
private List<T> list;
/**
* 返回空分页结果
* @param p MybatisPlus的分页结果
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> empty(Page<P> p){
return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());
}
/**
* 将MybatisPlus分页结果转为 VO分页结果
* @param p MybatisPlus的分页结果
* @param voClass 目标VO类型的字节码
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {
// 1.非空校验
List<P> records = p.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = BeanUtil.copyToList(records, voClass);
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
/**
* 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式
* @param p MybatisPlus的分页结果
* @param convertor PO到VO的转换函数
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {
// 1.非空校验
List<P> records = p.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = records.stream().map(convertor).collect(Collectors.toList());
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
}
Service层的业务代码可以简化为:
@Override
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
// 1.构建条件
Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();
// 2.查询
page(page);
// 3.封装返回
return PageDTO.of(page, UserVO.class);
}
如果是希望自定义PO到VO的转换过程,则可以在封装这里自己定义:
@Override
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
// 1.构建条件
Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();
// 2.查询
page(page);
// 3.封装返回
return PageDTO.of(page, user -> {
// 拷贝属性到VO
UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
// 用户名脱敏
String username = vo.getUsername();
vo.setUsername(username.substring(0, username.length() - 2) + "**");
return vo;
});
}
这里在开发过程当中可以简化很多操作,非常好用。
声明:学习视频参考B站黑马程序员(MybatisPlus-01.MybatisPlus介绍哔哩哔哩bilibili),MyBatis-Plus介绍等部分内容参考ChatGPT,详细笔记参考(day01-MybatisPlus - 飞书云文档 (feishu.cn))