扩展和插件功能
生成代码
下载好之后要重启IDEA
连接好数据库
具体操作:
然后就可以直接生成代码了
静态工具
有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db
,其中的一些静态方法与IService
中方法签名基本一致,也可以实现CRUD功能:
案例;:
需求:
①改造根据id查询用户的接口,查询用户的同时,查询出用户对应的所有地址
②改造根据id批量查询用户的接口,查询用户的同时,查询出用户对应的所有地址
③实现根据用户id查询收货地址功能,需要验证用户状态,冻结用户抛出异常(练习)
操作:
UserMapper
接口
继承 MyBatis - Plus 的 BaseMapper
,获得通用 CRUD 能力:
package com.itheima.mp.domain.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper extends BaseMapper<User> {
}
AddressMapper
接口
同样继承 BaseMapper
,假设 Address
实体类有 userId
关联用户 ID:
package com.itheima.mp.domain.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.Address;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface AddressMapper extends BaseMapper<Address> {
}
UserService
接口
package com.itheima.mp.domain.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.po.Address;
import java.util.List;public interface UserService extends IService<User> {// 需求1:根据id查询用户及地址User getUserWithAddressesById(Long id);// 需求2:根据id批量查询用户及地址List<User> listUsersWithAddressesByIds(List<Long> ids);// 需求3:根据用户id查询收货地址,验证状态List<Address> getAddressesByUserIdWithStatusCheck(Long userId) throws UserFrozenException;
}// 自定义冻结异常
class UserFrozenException extends RuntimeException {public UserFrozenException(String message) {super(message);}
}
Service 实现类
package com.itheima.mp.domain.service.impl;import com.baomidou.mybatisplus.core.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.SqlHelper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.mapper.AddressMapper;
import com.itheima.mp.domain.mapper.UserMapper;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.po.Address;
import com.itheima.mp.domain.service.UserService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {private final AddressMapper addressMapper;public UserServiceImpl(AddressMapper addressMapper) {this.addressMapper = addressMapper;}// 需求1:根据id查询用户及地址,用 MyBatis - Plus 静态工具 Wrappers 构造条件(这里查用户用 getById,也可演示 wrapper )@Overridepublic User getUserWithAddressesById(Long id) {// 方式1:直接用 IService 提供的 getById(底层已封装常用查询 )User user = getById(id);if (SqlHelper.retBool(user)) { // SqlHelper.retBool 辅助判断查询结果是否有效// 查地址:用 Wrappers.lambdaQuery 构造条件,根据 userId 关联List<Address> addresses = addressMapper.selectList(Wrappers.lambdaQuery(Address.class).eq(Address::getUserId, id));user.setAddresses(addresses);}return user;}// 需求2:批量查询用户及地址,结合 MyBatis - Plus 批量查询 + 地址关联@Overridepublic List<User> listUsersWithAddressesByIds(List<Long> ids) {// 批量查用户:用 IService 的 listByIdsList<User> userList = listByIds(ids);if (!SqlHelper.isEmpty(userList)) { // 辅助判断集合是否非空List<Long> userIds = userList.stream().map(User::getId).collect(Collectors.toList());// 批量查地址:用 Wrappers.lambdaQuery 构造 in 条件List<Address> addressList = addressMapper.selectList(Wrappers.lambdaQuery(Address.class).in(Address::getUserId, userIds));// 关联地址到用户:遍历用户,匹配地址userList.forEach(user -> {List<Address> userAddresses = addressList.stream().filter(address -> address.getUserId().equals(user.getId())).collect(Collectors.toList());user.setAddresses(userAddresses);});}return userList;}// 需求3:查询地址并校验用户状态,用 MyBatis - Plus 工具简化逻辑@Overridepublic List<Address> getAddressesByUserIdWithStatusCheck(Long userId) throws UserFrozenException {// 查用户:用 Wrappers.lambdaQuery 构造条件(也可用 getById,演示不同方式 )User user = getOne(Wrappers.lambdaQuery(User.class).eq(User::getId, userId));if (SqlHelper.retBool(user)) {if (user.getStatus() == 2) { // 状态2为冻结,抛异常throw new UserFrozenException("用户已冻结,无法查询地址");}// 查地址return addressMapper.selectList(Wrappers.lambdaQuery(Address.class).eq(Address::getUserId, userId));}return List.of();}
}
Controller 层
package com.itheima.mp.domain.controller;import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.po.Address;
import com.itheima.mp.domain.service.UserService;
import com.itheima.mp.domain.service.UserFrozenException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;@RestController
public class UserController {private final UserService userService;public UserController(UserService userService) {this.userService = userService;}// 需求1:根据id查询用户及地址@GetMapping("/user/{id}")public User getUserWithAddresses(@PathVariable Long id) {return userService.getUserWithAddressesById(id);}// 需求2:批量查询用户及地址@GetMapping("/users")public List<User> listUsersWithAddresses(@RequestParam List<Long> ids) {return userService.listUsersWithAddressesByIds(ids);}// 需求3:查询地址并校验状态@GetMapping("/user/address/{userId}")public List<Address> getAddressesWithStatusCheck(@PathVariable Long userId) {try {return userService.getAddressesByUserIdWithStatusCheck(userId);} catch (UserFrozenException e) {// 实际可返回统一错误响应,这里简单处理e.printStackTrace();return List.of();}}
}
逻辑删除
逻辑删除是基于代码逻辑模拟删除效果,不会真正删除数据,核心思路:
- 表中添加字段(如
deleted
)标记数据是否被删除 - 删除操作时,将标记字段置为“已删除值”(如
1
) - 查询操作时,自动过滤已删除数据(只查标记为“未删除值”,如
0
的数据 )
例如:
- 删除操作:执行更新语句,标记数据为已删除
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0;
(通过 AND deleted = 0
避免重复标记,保证幂等性 )
- 查询操作:自动附加条件,只查未删除数据
SELECT * FROM user WHERE deleted = 0;
MyBatis - Plus 可自动实现逻辑删除,自动增强 CRUD 语句,无需修改业务代码,只需在配置中声明逻辑删除规则:
1. 配置(application.yml
示例 )
mybatis-plus:global-config:db-config:logic-delete-field: flag # 全局逻辑删除实体字段名(需与实体类、数据库字段一致)logic-delete-value: 1 # 逻辑已删除值(如 1 代表“已删除”)logic-not-delete-value: 0# 逻辑未删除值(如 0 代表“未删除”)
2. 效果
- 删除方法:调用
removeById(1)
时,MyBatis - Plus 自动转为更新语句
UPDATE user SET flag = 1 WHERE id = 1 AND flag = 0;
- 查询方法:调用
list()
时,自动附加条件
SELECT * FROM user WHERE flag = 0;
- 无需修改业务代码:Service、Mapper 层调用原方法(如
remove
、list
)即可,底层自动处理逻辑删除规则
虽然 MyBatis - Plus 简化了逻辑删除实现,但存在以下缺陷:
- 数据膨胀:未真正删除数据,表中“垃圾数据”会持续积累,长期影响查询效率
- 查询性能:所有查询需附加
WHERE deleted = 0
条件,增加 SQL 复杂度
替代方案:若数据不能物理删除,可考虑
- 数据归档:定期迁移历史数据到归档表
- 软删除 + 定期清理:结合定时任务,物理删除超期的逻辑删除数据
枚举处理器
像这种字段一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是数据库采用的是int类型,对应的PO也是Integer。因此业务操作时必须手动把枚举与Integer转换,非常麻烦;
因此,MybatisPlus提供了一个处理枚举的类型转换器,可以把枚举类型与数据库类型自动转换。
定义状态枚举类
定义一个枚举类来表示 status
字段的含义,清晰管理状态值和对应的描述。比如:
import lombok.Getter;// 定义用户状态枚举
@Getter
public enum UserStatusEnum {NORMAL(1, "正常"),FROZEN(2, "冻结");@EnumValueprivate final int value;@JsonValueprivate final String desc;UserStatus(int value, String desc) {this.value = value;this.desc = desc;}
}
配置枚举类型处理器
mybatis:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
然后把类型改为UserStatusEnum
这样就可以实现转换了
后端给前端默认返回枚举变量的名称,即NORMAL
和FREEZE
。可以在字段上加上@JsonValue
注解,使后端返回对应的枚举变量的字段,此处返回正常
或冻结
:
JASON处理器
MybatisPlus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理JSON就可以使用JacksonTypeHandler
处理器来转换实体类
实体类上面要加上:
@TableName(value = "user", autoResultMap = true)
字段上面要加上:
@TableField(typeHandler = JacksonTypeHandler.class)
作用:将 JSON 数据结构化,方便业务代码操作,避免直接处理 JSON 字符串
插件功能
MybatisPlus提供了很多的插件功能,进一步拓展其功能。目前已有的插件有:
- PaginationInnerInterceptor:自动分页
- TenantLineInnerInterceptor:多租户
- DynamicTableNameInnerInterceptor:动态表名
- OptimisticLockerInnerInterceptor:乐观锁
- IllegalSQLInnerInterceptor:sql 性能规范
- BlockAttackInnerInterceptor:防止全表更新与删除
注意: 使用多个分页插件的时候需要注意插件定义顺序,建议使用顺序如下:
- 多租户,动态表名
- 分页,乐观锁
- sql 性能规范,防止全表更新与删除