新闻详情

新闻详情

首页 / 资讯中心 / 详情

MyBatis-Plus入门指南:提升CRUD效率的实战技巧

发布时间:2026/9/11 6:37:19来源:尧图网络
MyBatis-Plus入门指南:提升CRUD效率的实战技巧
1. MyBatis-Plus入门为什么选择它而不是原生MyBatis我第一次接触MyBatis-Plus是在2018年接手一个电商后台项目时。当时团队正在为要不要引入这个增强工具争论不休——有人担心增加学习成本有人认为会带来维护风险。但实际使用后我们项目组的CRUD开发效率提升了近60%特别是面对复杂业务表单时原先需要2-3天完成的DAO层工作现在半天就能搞定。MyBatis-Plus简称MP本质上是对MyBatis的扩展封装最新稳定版是3.5.17截至2024年1月与Spring Boot 2.7.x版本兼容性最佳。它的核心价值在于通过内置通用Mapper和Service让开发者用极简代码实现90%的数据库操作。举个例子传统MyBatis查询用户列表需要// 原生MyBatis方式 Select(SELECT * FROM user WHERE deleted0) ListUser selectActiveUsers();而在MP中只需要// MyBatis-Plus方式 userService.lambdaQuery() .eq(User::getDeleted, 0) .list();这种改进不仅仅是代码量的减少。MP的lambda表达式写法在编译期就会检查实体类字段是否存在避免了手写SQL字符串容易出现的字段名拼写错误。我曾在排查一个线上Bug时发现团队之前用原生MyBatis时因字段名拼错导致的SQL异常占数据库错误的37%改用MP后这类错误直接归零。2. 环境搭建与基础配置2.1 依赖引入与版本匹配当前最稳定的组合是Spring Boot 2.7.18MyBatis-Plus 3.5.17MySQL 8.0.33Maven配置示例dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.17/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency注意Spring Boot 3.x用户需要使用MP的5.x版本但截至2024年初5.x的生态插件如代码生成器还不够完善生产环境建议仍采用2.7.xMP3.5.x的组合。2.2 实体类注解详解MP通过注解实现ORM映射这是与JPA相似但更灵活的方式。以用户实体为例Data TableName(sys_user) // 实际表名 public class User { TableId(type IdType.AUTO) // 自增主键 private Long id; TableField(username) // 字段映射 private String name; TableField(exist false) // 非表字段 private String tempToken; Version // 乐观锁版本字段 private Integer version; }踩坑提醒当你的字段名是数据库关键字时如order、desc必须使用反引号转义TableField(order) private Integer order;3. 核心CRUD操作实战3.1 插入操作的三种姿势基础插入User user new User(); user.setName(张三); userMapper.insert(user); // 返回影响行数批量插入优化MP默认的批量插入其实是循环单条插入真正的高效批量插入需要配置# application.yml mybatis-plus: global-config: db-config: logic-delete-field: deleted # 逻辑删除字段名 id-type: auto # 主键策略 configuration: default-executor-type: batch # 启用批处理模式然后使用ListUser users ...; userService.saveBatch(users, 1000); // 每1000条提交一次带主键回写的高级插入User user new User(); user.setName(李四); userMapper.insert(user); System.out.println(user.getId()); // 自动回写自增ID3.2 查询的链式调用艺术MP的lambda查询是最大亮点这种写法比JPA的Criteria API更直观ListUser users userService.lambdaQuery() .like(User::getName, 张) // 模糊查询 .between(User::getAge, 18, 30) // 范围查询 .orderByDesc(User::getCreateTime) // 排序 .list();复杂查询示例包含子查询ListUser users userService.lambdaQuery() .inSql(User::getDepartmentId, SELECT id FROM department WHERE parent_id 2) .list();性能提示当查询结果可能很大时务必使用page()分页而非list()全量查询否则可能引发OOM。3.3 更新操作的两种策略全量更新User user userService.getById(1L); user.setName(王五); userService.updateById(user); // 更新所有字段动态更新推荐userService.lambdaUpdate() .eq(User::getId, 1L) .set(User::getName, 赵六) .set(User::getAge, 25) .update();特殊场景乐观锁更新User user userService.getById(1L); user.setName(钱七); user.setVersion(user.getVersion()); // 带上当前版本号 userService.updateById(user); // 内部会检查版本一致性3.4 删除操作的业务考量物理删除慎用userMapper.deleteById(1L); // 直接从数据库删除逻辑删除推荐首先在实体类标记TableLogic private Integer deleted; // 1-已删除 0-未删除然后删除操作变为更新userMapper.deleteById(1L); // 实际执行UPDATE SET deleted1查询时会自动过滤已删除数据userService.list(); // 自动附加WHERE deleted0条件4. 高级特性与性能优化4.1 自动填充的优雅实现处理create_time/update_time等字段的自动填充实体类注解TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;实现MetaObjectHandlerComponent public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }4.2 分页查询的防坑指南MP的分页需要先配置拦截器Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }使用示例PageUser page new Page(1, 10); // 当前页每页条数 PageUser result userService.lambdaQuery() .like(User::getName, 张) .page(page);常见问题当使用left join时total计数会不准确需要自定义count sql大数据量分页避免使用page(pageNum, pageSize)而应该用page(Page page)重用Page对象4.3 多数据源下的特殊处理在Spring Boot多数据源场景中MP需要特殊配置Bean ConfigurationProperties(spring.datasource.druid.master) public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } Bean ConfigurationProperties(spring.datasource.druid.slave) public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } Bean public DynamicDataSource dynamicDataSource() { MapObject, Object dataSourceMap new HashMap(); dataSourceMap.put(master, masterDataSource()); dataSourceMap.put(slave, slaveDataSource()); return new DynamicDataSource(masterDataSource(), dataSourceMap); } Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 注意多数据源时需要为每个数据源单独配置分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL){ Override public DataSource findDataSource(String sql) { return DynamicDataSourceContextHolder.peek() ! null ? dynamicDataSource().getDataSource(DynamicDataSourceContextHolder.peek()) : dynamicDataSource().getDefaultDataSource(); } }); return interceptor; }5. 实战中的血泪教训5.1 N1查询问题即使使用MP也可能遇到经典的N1查询问题。例如ListOrder orders orderService.list(); orders.forEach(order - { User user userService.getById(order.getUserId()); order.setUser(user); });解决方案使用TableField(exist false) 手动join查询或者使用MP的TableField(select false)延迟加载需要额外配置5.2 大事务下的批量操作在一次处理10万条数据的场景中直接使用saveBatch会导致事务过大。正确的做法Transactional(rollbackFor Exception.class) public void batchProcess(ListUser users) { // 每500条提交一次 int batchSize 500; for (int i 0; i users.size(); i batchSize) { ListUser subList users.subList(i, Math.min(i batchSize, users.size())); userService.saveBatch(subList); // 手动清空会话缓存防止OOM SqlSessionHelper.clearCache(userService.getBaseMapper()); } }5.3 字段类型映射的坑MySQL的datetime类型映射到Java的LocalDateTime时如果数据库时区与系统时区不一致会出现时间偏差。解决方案spring: datasource: url: jdbc:mysql://localhost:3306/db?serverTimezoneAsia/ShanghaiuseSSLfalse对于JSON字段存储推荐使用MP的TypeHandlerTableField(typeHandler JacksonTypeHandler.class) private MapString, Object attributes;5.4 逻辑删除的联表查询当主表逻辑删除关联表需要手动处理Select(SELECT u.* FROM user u LEFT JOIN department d ON u.dept_id d.id WHERE u.deleted 0 AND d.deleted 0) ListUser selectActiveUsersWithDepartment();或者使用MP的SqlParser注解过滤SqlParser(filter true) public interface UserMapper extends BaseMapperUser { Select(SELECT u.* FROM user u LEFT JOIN department d ON u.dept_id d.id WHERE d.deleted 0) ListUser selectUsersWithActiveDepartment(); }6. 与前端框架的协作实践6.1 配合Vue3实现CRUD前端常见的查询参数结构{ current: 1, size: 10, params: { name: 张, status: 1 } }后端接收方式PostMapping(/page) public RPageUser page(RequestBody QueryDTO queryDTO) { LambdaQueryWrapperUser wrapper Wrappers.lambdaQuery(); wrapper.like(StringUtils.isNotBlank(queryDTO.getParams().getName()), User::getName, queryDTO.getParams().getName()); wrapper.eq(queryDTO.getParams().getStatus() ! null, User::getStatus, queryDTO.getParams().getStatus()); return R.success(userService.page(new Page(queryDTO.getCurrent(), queryDTO.getSize()), wrapper)); }6.2 接口返回格式标准化建议统一返回结构Data public class RT implements Serializable { private Integer code; private String msg; private T data; private Long timestamp System.currentTimeMillis(); public static T RT success(T data) { RT r new R(); r.setCode(200); r.setData(data); return r; } public static T RT error(String msg) { RT r new R(); r.setCode(500); r.setMsg(msg); return r; } }6.3 参数校验的最佳实践结合Validation注解使用Data public class UserDTO { NotBlank(message 用户名不能为空) Size(min 2, max 20, message 用户名长度2-20位) private String username; NotNull(message 部门ID不能为空) private Long deptId; } PostMapping(/save) public RString save(Valid RequestBody UserDTO userDTO) { User user new User(); BeanUtils.copyProperties(userDTO, user); userService.save(user); return R.success(保存成功); }对于复杂校验如手机号唯一性建议在Service层实现Override public boolean save(User user) { if (lambdaQuery().eq(User::getPhone, user.getPhone()).exists()) { throw new BusinessException(手机号已存在); } return super.save(user); }
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

序列绑定问题全解:从列表key到批量导入的坑与根治方案 2026/9/11 7:16:24

序列绑定问题全解:从列表key到批量导入的坑与根治方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
GHelper:如何用一个 exe 轻量控制华硕笔记本,完整指南 2026/9/11 7:16:24

GHelper:如何用一个 exe 轻量控制华硕笔记本,完整指南

GHelper:如何用一个 exe 轻量控制华硕笔记本,完整指南 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, …

阅读更多 →
智能水位监测技术在现代水稻种植中的应用与选型指南 2026/9/11 7:16:24

智能水位监测技术在现代水稻种植中的应用与选型指南

1. 稻田水位监测的核心价值与行业痛点在现代化农业生产中,水稻种植的水位管理一直是影响产量和品质的关键因素。传统的水稻灌溉主要依靠人工观察和经验判断,这种方式存在几个明显的缺陷:首先,人工监测无法实现24小时不间断的水位监…

阅读更多 →
Midscene.js 教程:让 AI 接管你的浏览器 5 分钟上手 2026/9/11 7:16:23

Midscene.js 教程:让 AI 接管你的浏览器 5 分钟上手

Midscene.js 教程:让 AI 接管你的浏览器 5 分钟上手 【免费下载链接】midscene GUI Agent for E2E Testing 项目地址: https://gitcode.com/GitHub_Trending/mid/midscene 上周我又中招了:回归脚本卡在第 7 步——前端顺手改了按钮的 class&#…

阅读更多 →
Chat2DB 社区贡献指南:从 Issue 认领到代码合入的协作流程与工程实践 2026/9/11 7:16:23

Chat2DB 社区贡献指南:从 Issue 认领到代码合入的协作流程与工程实践

Chat2DB 社区贡献指南:从 Issue 认领到代码合入的协作流程与工程实践 【免费下载链接】Chat2DB Chat2DB is a free, cross-platform, local-first database client and SQL workspace for developers, DBAs, analysts, and data teams. Connect to 40 databases, ma…

阅读更多 →
多时段多公司需求响应管理系统:从基线计算到考核结算的实战指南 2026/9/11 7:13:23

多时段多公司需求响应管理系统:从基线计算到考核结算的实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞