ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MyBatis-Plus 从入门到实战:常用功能与核心原理

MyBatis-Plus 从入门到实战:常用功能与核心原理 前言在 Java 后端开发中MyBatis 是非常常见的持久层框架。不过实际开发中我们经常会写大量重复的单表 CRUD例如insert deleteById updateById selectById selectList这些代码在不同项目中往往都差不多。MyBatis-Plus简称 MP就是在 MyBatis 基础上进行增强的工具。它的核心理念是只做增强不做改变。也就是说简单 CRUD 可以交给 MyBatis-Plus复杂 SQL 依然可以继续使用 MyBatis XML。简单理解MyBatis-Plus MyBatis 通用 CRUD 条件构造器 分页 逻辑删除 自动填充 常用插件下面主要介绍日常开发中最常使用的一些功能。一、基本使用在 Spring Boot 项目中引入 MyBatis-Plus 后假设有一张用户表CREATETABLEuser(idBIGINTPRIMARYKEY,usernameVARCHAR(50),ageINT,statusINT,deletedINTDEFAULT0,versionINTDEFAULT1,create_timeDATETIME,update_timeDATETIME);对应实体类DataTableName(user)publicclassUser{TableIdprivateLongid;privateStringusername;privateIntegerage;privateIntegerstatus;privateIntegerdeleted;privateIntegerversion;privateLocalDateTimecreateTime;privateLocalDateTimeupdateTime;}常见注解包括TableName 表名映射 TableId 主键 TableField 字段映射 TableLogic 逻辑删除 Version 乐观锁二、BaseMapper简化 CRUD创建 MapperMapperpublicinterfaceUserMapperextendsBaseMapperUser{}只需要继承BaseMapperUser就可以直接使用常见 CRUD。新增UserusernewUser();user.setUsername(张三);user.setAge(20);userMapper.insert(user);根据 ID 查询UseruseruserMapper.selectById(1L);修改UserusernewUser();user.setId(1L);user.setUsername(李四);userMapper.updateById(user);删除userMapper.deleteById(1L);查询全部ListUserusersuserMapper.selectList(null);这也是 MyBatis-Plus 最直观的优势很多简单 SQL 不需要重复手写。三、IService实际项目一般会按照Controller ↓ Service ↓ Mapper ↓ Database这样的结构开发。Service 可以继承publicinterfaceUserServiceextendsIServiceUser{}实现类ServicepublicclassUserServiceImplextendsServiceImplUserMapper,UserimplementsUserService{}之后可以直接使用userService.save(user);userService.getById(1L);userService.list();userService.removeById(1L);userService.updateById(user);还有saveBatch()saveOrUpdate()page()count()不过复杂业务还是建议定义语义明确的方法例如freezeUser();cancelOrder();refundOrder();而不是所有业务都直接调用通用 CRUD。四、QueryWrapper 条件查询MyBatis-Plus 中非常常用的一个功能就是 Wrapper。例如查询年龄大于等于 18并且状态正常的用户。可以写QueryWrapperUserwrappernewQueryWrapper();wrapper.ge(age,18).eq(status,1);ListUserlistuserMapper.selectList(wrapper);常见条件eq ne ! gt ge lt le like LIKE in IN between BETWEEN还可以orderByAsc();orderByDesc();进行排序。五、更推荐 LambdaQueryWrapper普通 QueryWrapper 有一个问题wrapper.eq(username,张三);字段名是字符串。如果以后字段发生变化IDE 重构不一定能够发现这里的问题。因此实际项目中更推荐LambdaQueryWrapperUserwrappernewLambdaQueryWrapper();wrapper.eq(User::getUsername,张三).ge(User::getAge,18).eq(User::getStatus,1);ListUserlistuserMapper.selectList(wrapper);相比username这种写法User::getUsername更加安全也更方便重构。所以我一般更推荐LambdaQueryWrapper LambdaUpdateWrapper六、动态查询Wrapper 很适合开发搜索接口。例如前端传Stringusername;Integerstatus;Integerage;可以写LambdaQueryWrapperUserwrappernewLambdaQueryWrapper();wrapper.like(StringUtils.hasText(username),User::getUsername,username).eq(status!null,User::getStatus,status).ge(age!null,User::getAge,age);当条件不满足时对应 SQL 条件不会被拼接。相比大量if(...){wrapper.eq(...);}代码会更加简洁。七、LambdaUpdateWrapper除了查询Wrapper 也可以用于更新。例如禁用 id 为 1 的用户。LambdaUpdateWrapperUserwrappernewLambdaUpdateWrapper();wrapper.eq(User::getId,1L).set(User::getStatus,0);userMapper.update(null,wrapper);最终 SQL 类似UPDATEuserSETstatus0WHEREid1;对于只修改少数字段的情况这种方式比较方便。八、分页查询后台管理系统通常都会使用分页。配置ConfigurationpublicclassMybatisPlusConfig{BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptornewMybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));returninterceptor;}}查询PageUserpagenewPage(1,10);PageUserresultuserMapper.selectPage(page,null);其中1 当前页 10 每页条数分页结果可以获取result.getRecords();result.getTotal();result.getCurrent();result.getSize();result.getPages();如果带条件LambdaQueryWrapperUserwrappernewLambdaQueryWrapper();wrapper.eq(User::getStatus,1).orderByDesc(User::getCreateTime);PageUserresultuserMapper.selectPage(newPage(1,10),wrapper);九、逻辑删除实际项目中有些数据不能真正删除。例如用户、文章、订单等数据可能需要保留历史记录。这时可以使用逻辑删除。数据库字段deleted 0 正常 1 已删除实体中TableLogicprivateIntegerdeleted;执行userMapper.deleteById(1L);表面上是删除实际 SQL 类似UPDATEuserSETdeleted1WHEREid1;之后正常查询时会自动过滤已经逻辑删除的数据。需要注意逻辑删除和业务状态不是一回事。例如订单待支付 已支付 已取消 已完成应该使用status表示而不是使用deleted。十、自动填充很多数据库表都有create_time update_time如果每次手动赋值会比较麻烦。可以使用 MyBatis-Plus 自动填充。实体TableField(fillFieldFill.INSERT)privateLocalDateTimecreateTime;TableField(fillFieldFill.INSERT_UPDATE)privateLocalDateTimeupdateTime;然后实现ComponentpublicclassMyMetaObjectHandlerimplementsMetaObjectHandler{OverridepublicvoidinsertFill(MetaObjectmetaObject){this.strictInsertFill(metaObject,createTime,LocalDateTime.class,LocalDateTime.now());this.strictInsertFill(metaObject,updateTime,LocalDateTime.class,LocalDateTime.now());}OverridepublicvoidupdateFill(MetaObjectmetaObject){this.strictUpdateFill(metaObject,updateTime,LocalDateTime.class,LocalDateTime.now());}}除了时间还可以用于createUser updateUser等公共字段。十一、乐观锁假设两个用户同时修改同一条数据。用户 A 查询到version 1用户 B 查询时也是version 1如果两个人同时提交修改可能发生数据覆盖。MyBatis-Plus 可以使用乐观锁。实体VersionprivateIntegerversion;配置interceptor.addInnerInterceptor(newOptimisticLockerInnerInterceptor());更新时 SQL 思路类似UPDATEuserSETusername新的名字,version2WHEREid1ANDversion1;如果其他线程已经把版本改成version 2那么version 1这个条件就无法匹配从而发现并发冲突。十二、MybatisPlusInterceptor前面的分页、乐观锁其实都属于 MyBatis-Plus 的插件能力。核心入口是MybatisPlusInterceptor例如MybatisPlusInterceptorinterceptornewMybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor());interceptor.addInnerInterceptor(newOptimisticLockerInnerInterceptor());除了这些还可以实现多租户 动态表名 数据权限 防止全表更新和删除等功能。因此 MyBatis-Plus 不只是简单地帮我们少写 CRUD也提供了很多数据库访问层的公共能力。十三、BaseMapper 为什么没有实现类我们只写publicinterfaceUserMapperextendsBaseMapperUser{}却没有UserMapperImpl为什么可以执行这里可以简单理解为两个部分。1. MyBatis 动态代理MyBatis 会在运行时为 Mapper 接口创建代理对象。所以userMapper.selectById(1L);实际上调用的是 MyBatis 生成的代理对象。2. MyBatis-Plus SQL InjectorMyBatis-Plus 在启动时会把insert deleteById updateById selectById selectList这些通用 CRUD 注册进去。整体流程可以简单理解为UserMapper ↓ BaseMapper ↓ MyBatis-Plus 注册通用 SQL ↓ MyBatis Mapper 动态代理 ↓ Executor ↓ JDBC ↓ 数据库所以 MyBatis-Plus 底层依然建立在 MyBatis 之上。十四、复杂 SQL 应该怎么办使用 MyBatis-Plus 之后一个常见误区是是不是以后都不用写 SQL 了答案当然不是。例如SELECT*FROMuserWHEREstatus1;这种简单查询非常适合 Wrapper。但是如果涉及多表 JOIN GROUP BY 复杂聚合 子查询 统计报表 窗口函数如果强行使用 Wrapper 拼接可读性可能反而下降。这时候直接使用Mapper XML通常更加清晰。所以我个人更推荐简单 CRUD ↓ BaseMapper / IService 动态条件 ↓ LambdaQueryWrapper 动态更新 ↓ LambdaUpdateWrapper 复杂 SQL ↓ Mapper XML十五、实际开发中的几个注意点1. 尽量使用 Lambda Wrapper少写wrapper.eq(username,username);推荐wrapper.eq(User::getUsername,username);可以减少字段名写错的问题。2. 不要直接相信前端传来的 SQL 字段例如wrapper.orderByAsc(sortField);如果sortField完全由前端控制就存在安全风险。更合理的方式是做字段白名单username createTime id只允许指定字段参与排序。3. 不要滥用 saveOrUpdatesaveOrUpdate(user);确实方便。但是很多业务应该明确区分新增 修改例如createUser();updateUser();业务语义会更加清晰。4. Wrapper 太复杂时直接写 SQL如果 Wrapper 已经写成.and(...).or(...).nested(...).exists(...)并且自己都很难一眼看懂最终 SQL那么通常直接写 XML 会更加合适。工具是为了降低复杂度而不是增加复杂度。十六、总结MyBatis-Plus 本质上是对 MyBatis 的增强。它比较适合解决CRUD 分页 动态查询 逻辑删除 自动填充 乐观锁这些大量重复出现的开发需求。常用学习路线可以概括为BaseMapper ↓ IService ↓ LambdaQueryWrapper ↓ LambdaUpdateWrapper ↓ 分页 ↓ 逻辑删除 ↓ 自动填充 ↓ 乐观锁 ↓ MybatisPlusInterceptor但 MyBatis-Plus 并不是为了完全取代 SQL。比较合理的使用方式应该是简单 CRUD 交给 MyBatis-Plus 复杂 SQL 交给 MyBatis 业务逻辑 放在 Service 数据一致性 依靠数据库设计和事务我认为 MyBatis-Plus 最大的价值不是让我们以后不用写 SQL。而是让我们不用再重复写那些没有必要手写的 SQL。理解这一点之后MyBatis-Plus 才真正算是用对了。
返回列表