mp
MybatisPlus
MybatisPlus 是基于 MyBatis 的增强工具,它简化了 MyBatis 的开发,并提供了一些常用的自动化功能,如 CRUD 操作的自动生成
MybatisPlus 的目标是使得开发者 不再编写重复的 SQL 语句,同时保留 MyBatis 的原有功能,使得 MyBatis 更加轻量、简洁、高效
设计表结构:
balance n.用户余额
引入 MP 依赖
注:mp 依赖包含了 mp 和 mybatis,所以可以删除 mybatis 的依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
</dependency>
继承 BaseMapper<T>
这样 mapper 就拥有了 BaseMapper 内置的增删改查方法,简单的方法可以从 BaseMapper 直接继承使用
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
内置方法如:
在 service 层,UserService 拥有了 BaseService 的方法 -> 直接调用
@Service
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
public void saveUser(User user) {
userMapper.insert(user);
}
MybatisPlus 原理
MybatisPlus 可以自动增删改查的原理:
通过扫描实体类,并基于反射获取实体类信息作为数据库表信息,然后通过 约定 获得数据库字段信息
约定 大于 配置:
- 类名驼峰转下划线作为表名
- 名为 id 的字段作为主键
- 变量名驼峰转下划线作为字段名
如果不遵守约定,可用注解:
注解 | 作用 |
---|---|
@TableName | 用来指定表名 |
@Tableld | 用来指表中的主键字段信息 |
@TableField | 用来指定表中的普通字段信息 |
配置
mybatis-plus:
type-aliases-package: com.wyn.springcloud.pojo # 别名扫描包
configuration:
map-underscore-to-camel-case: true # 是否开启下划线和驼峰的映射
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
global-config:
db-config:
id-type: assign_id # id为雪花算法生成
update-strategy: not_null # 更新策略:只更新非空字段
条件构造器
BaseMapper
接口含有一些方法,形参 Wrapper
是条件构造器
Wrapper
包装器
Wrapper
的继承关系:
1.5.1 QueryWrapper 查询
查找名字带o
,余额大于等于 1000 元的id
、usernam
、info
、balance
字段
eq
:等于
ne
:不等于
ge
:大于等于
gt
:大于
le
:小于等于
lt
:小于
public List<User> ListByUsername(String s) {
QueryWrapper<User> queryWrapper = new QueryWrapper<User>()
.select("id", "username", "balance", "info")
.like("username", s)
.ge("balance", "1000");
List<User> users = userMapper.selectList(queryWrapper);
return users;
}
1.5.2 UpdateWrapper 更新
- 更新 jack 的余额为 2000
更新方法的传参有两个:一个是 用户实体,一个是 updateWrapper
/**
* balance set to 2000 by username
*
* @param username
*/
@Override
public void updateBalanceByUsername(String username) {
User user = User.builder()
.balance(2000)
.build();
UpdateWrapper updateWrapper = new UpdateWrapper<>()
.eq("username", username);
userMapper.update(user, updateWrapper);
}
这里注意实体类规定了更新的字段,装饰器规定了要更新的人,是与逻辑相反的
- 更新
id
为1, 2, 3
的用户,余额扣 200
// controller
/**
* balance minus 100 by ids
* @param ids
* @return
*/
@GetMapping("/minusBalance")
public Result MinusBalanceByIds(@RequestParam List<Long> ids){ // notice this annotation // @RequestParam
userService.minusBalanceByIds(ids);
return Result.success();
}
// service
public void minusBalanceByIds(List<Long> ids) {
UpdateWrapper<User> updateWrapper = new UpdateWrapper<User>()
.in("id", ids)
.setSql("balance = balance - 100");
userMapper.update(null, updateWrapper);
}
对于 List 这样的集合类型需要加上注解 @RequestParam,而普通的数据类型则不用
IService 接口
Mybatis 提供了 IService
和其实现类 ServiceImpl
UserService 继承 IService
UserServiceImpl 继承 ServiceImpl
,加上泛型,然后实现 UserService
public interface UserService extends IService<User>
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService
由于 UserService 继承了 IService
,所以 UserService 拥有 ServiceImpl
的实现
UserMapper 继承 BaseMapper
public interface UserMapper extends BaseMapper<User>
这样就可以直接调用 ServiceImpl
的内置方法
/**
* get user by id
*
* @param id
* @return
*/
@GetMapping("/getById")
public Result<User> getById(Long id) {
return Result.success(userService.getById(id));
}
(补充)自动注入
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
}
编译器提示不建议使用字段注入
接下来使用构造函数
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
}
使用Lombok
简化:
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
}
使用 @RequiredArgsConstructor
注解时,Lombok 会自动生成一个包含所有 final
字段和使用 @NonNull
注解标记的字段的构造函数
综合应用
- 新增用户
/**
* save user by JSON
* @param userDTO
* @return
*/
@PostMapping
public Result saveUser(@RequestBody UserDTO userDTO) {
User user = new User();
BeanUtil.copyProperties(userDTO, user); // hutool
userService.save(user);
return Result.success();
}
需要引入
hutool
工具包
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.20</version> </dependency>
- 根据
id
批量查询
/**
* query users by ids
*
* @param ids
* @return
*/
@Override
public List<UserVO> selectByIds(List<Long> ids) {
List<UserVO> userVOList = new ArrayList<>();
ids.forEach(id -> {
UserVO userVO = new UserVO();
User user = userMapper.selectById(id);
BeanUtil.copyProperties(user, userVO);
userVOList.add(userVO);
});
return userVOList;
}
遍历 ids,通过 BaseMapper
的 selectById
一个一个找,然后加入 list
条件查询
username
:用户名关键字,可以为空
status
:用户状态,可以为空
minBalance
:最小余额,可以为空
maxBalance
:最大余额,可以为空
// Controller
/**
* query by name,status,maxBalance,minBalance
*
* @param userQueryDTO
* @return
*/
@PostMapping("/query")
public Result<List<UserVO>> queryByCondition(UserQueryDTO userQueryDTO) { // custom class
List<UserVO> userVOList = userService.queryByCondition(userQueryDTO);
return Result.success(userVOList);
}
// Service
public List<UserVO> queryByCondition(UserQueryDTO userQueryDTO) {
List<UserVO> userVOList = new ArrayList<>();
String username = userQueryDTO.getUsername();
Integer status = userQueryDTO.getStatus();
Integer maxBalance = userQueryDTO.getMaxBalance();
Integer minBalance = userQueryDTO.getMinBalance();
List<User> list = 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();
list.forEach(user -> {
UserVO userVO = new UserVO();
BeanUtil.copyProperties(user, userVO);
userVOList.add(userVO);
});
return userVOList;
}
DB 静态工具类
DB 是 mp 的静态工具类,为了防止 循环依赖
使用时可以 不注入 UserMapper
循环依赖(Circular Dependency)是指两个或多个对象(类、模块、Bean 等)相互依赖,形成一个循环的依赖关系。具体来说,如果对象 A 依赖对象 B,而对象 B 又依赖对象 A,这种情况就会导致循环依赖问题。循环依赖可能导致系统无法正确初始化,尤其是在依赖注入框架(如 Spring)中,会抛出异常,无法实例化相关的对象
加入一张地址表
在 UserVO
视图对象中,加入每个UserVO
对应的AddressVO
,有多个,所以用List<AddressVO>
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserVO {
// ...
private List<AddressVO> addressList;
}
查询User
和对应的Address
// Controller
/**
* get user and address by id
*
* @param id
* @return
*/
@GetMapping("/getById")
public Result<UserVO> getUserAndAddressById(Long id) {
return Result.success(userService.getUserAndAddressById(id));
}
// Service
public UserVO getUserAndAddressById(Serializable id) {
User user = getById(id);
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
List<Address> addressList = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, id)
.list();
// List<AddressVO> addressVOList = new ArrayList<>();
// addressList.forEach(address -> {
// AddressVO addressVO = BeanUtil.copyProperties(address, AddressVO.class);
// addressVOList.add(addressVO);
// });
// userVO.setAddressList(addressVOList);
userVO.setAddressList(BeanUtil.copyToList(addressList, AddressVO.class)); // reflection
return userVO;
}
这里使用 Db
的 lambdaQuery
方法,传参是查询的实体类的 字节码(反射)
避免了引入AddressMapper
造成 循环依赖
逻辑删除
逻辑删除 是一种软删除策略,即在数据库中并不直接物理删除数据行,而是通过设置某个 标志位 来标记该数据已被删除。这样做的好处是,数据不会从数据库中被永久删除,仍然可以恢复或用于历史查询
配置 logic-delete-field
: deleted
mybatis-plus:
type-aliases-package: com.wyn.springcloud.pojo # 别名扫描包
configuration:
map-underscore-to-camel-case: true # 是否开启下划线和驼峰的映射
global-config:
db-config:
id-type: assign_id # id为雪花算法生成
update-strategy: not_null # 更新策略:只更新非空字段
logic-delete-field: deleted # 逻辑删除字段名称
在User
中配置字段 deleted
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
// ...
@TableLogic
private Integer deleted;
}
分页查询
// 通用分页查询实体类
@Data
@ApiModel(description = "分页查询实体类")
public class PageQuery {
@ApiModelProperty("页码")
private Integer pageNo;
@ApiModelProperty("页数")
private Integer pageSize;
@ApiModelProperty("排序字段")
private String sortBy;
@ApiModelProperty("是否升序")
private Boolean isAsc;
}
// 用户查询实体类
@ApiModel(description = "user condition select entity")
@Data
public class UserQuery extends PageQuery{ // 继承通用类
private String name;
private Integer status;
private Integer minBalance;
private Integer maxBalance;
}
// 分页传输类
@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
@ApiModelProperty("总条数")
private Integer total;
@ApiModelProperty("总页数")
private Integer pages;
@ApiModelProperty("数据集合")
private List<T> list;
}
// Controller
@ApiOperation("条件分页查询")
@GetMapping("/page")
public Result<PageDTO<UserVO>> queryUserPage(UserQuery userQuery){
return Result.success(userService.queryUserPage(userQuery));
}
// Service
public PageDTO<UserVO> queryUserPage(UserQuery userQuery) {
String name = userQuery.getName();
Integer status = userQuery.getStatus();
Integer minBalance = userQuery.getMinBalance();
Integer maxBalance = userQuery.getMaxBalance();
Integer pageNo = userQuery.getPageNo();
Integer pageSize = userQuery.getPageSize();
String sortBy = userQuery.getSortBy();
Boolean isAsc = userQuery.getIsAsc();
Page<User> page = Page.of(pageNo, pageSize);
OrderItem orderItem = new OrderItem();
// 排序字段非空才可以排序
if (!sortBy.isEmpty()) {
orderItem.setColumn(sortBy);
orderItem.setAsc(isAsc);
}
page.addOrder(orderItem);
Page<User> p = lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.ge(maxBalance != null, User::getBalance, maxBalance)
.page(page);
// 构造返回结果
PageDTO<UserVO> dto = new PageDTO<>();
dto.setTotal(p.getTotal());
dto.setPages(p.getPages());
List<User> userList = p.getRecords();
if (userList.isEmpty()) {
dto.setList(null);
} else {
List<UserVO> userVOList = BeanUtil.copyToList(userList, UserVO.class);
dto.setList(userVOList);
}
return dto;
}
代码可以抽取,作为 PageQuery
和 PageDTO
的方法 -> 复用
这里为什么要返回
PageDTO
而不是Page
——mybatis 的分页类因为 有些字段不需要
public class Page<T> implements IPage<T> { private static final long serialVersionUID = 8545996863226528798L; protected List<T> records; protected long total; protected long size; protected long current; protected List<OrderItem> orders; protected boolean optimizeCountSql; protected boolean searchCount; protected boolean optimizeJoinOfCountSql; protected Long maxLimit; protected String countId; // ... }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人