2026/4/18 20:51:53
网站建设
项目流程
龙岗爱联网站建设,建站与备案的具体流程是什么,寓意好的商贸公司名字,电商平台如何做推广1、MyBatis-Plus 简介#xff1a;不止是增强#xff0c;更是重构
MyBatis-Plus 可以理解为「MyBatis 瑞士军刀皮肤 防删库保险栓」—— 它在保留 MyBatis 原生特性的基础上#xff0c;通过 零侵入 设计实现了单表操作的极简开发。
其核心价值体现在三方面不止是增强更是重构MyBatis-Plus 可以理解为「MyBatis 瑞士军刀皮肤 防删库保险栓」—— 它在保留 MyBatis 原生特性的基础上通过 零侵入 设计实现了单表操作的极简开发。其核心价值体现在三方面瑞士军刀般的便捷性将 XML 配置的 青铜时代 升级为 Lambda 表达式的 赛博坦时代用极简代码实现复杂操作保险栓级的安全性通过拦截器让 delete from table 这类危险操作成为不可能从源头避免删库风险零侵入的兼容性无需修改现有 MyBatis 代码老项目可平滑迁移既保留原生 SQL 灵活性又获得增强功能。2、核心功能单表操作的 全能工具箱MyBatis-Plus 的核心功能是围绕单表的 CRUD 展开的覆盖从基础操作到高级查询的全场景所有功能均通过BaseMapper接口暴露无需手动实现。2.1 基础 CRUD单表操作 零代码 实现BaseMapper 中封装了单表所有基础操作无需手写 SQL 就能满足 90% 的业务需求。关键方法整理如下操作类型方法示例作用注意事项新增int insert(T entity)插入一条记录返回影响行数自动忽略 entity 中 null 值的属性新增 / 更新boolean insertOrUpdate(T entity)若记录存在则更新否则插入依赖主键查询建议查主库避免主从延迟删除int deleteById(Serializable id)根据主键删除单条记录物理删除数据恢复需 DBA 协助删除int deleteByIds(Collection? idList)批量删除主键对应的记录需手动控制 idList 大小防止数据库负载过高更新int updateById(T entity)根据主键更新记录主键必须非空忽略 null 值属性避免 WHERE idNULL查询T selectById(Serializable id)根据主键查询单条记录-查询ListT selectList(WrapperT queryWrapper)根据条件批量查询queryWrapper 为 null 时会全表扫描需谨慎2.2 条件构造器复杂查询 优雅编码条件构造器是 MyBatis-Plus 的 灵魂支持用面向对象的方式构建 SQL 条件避免字符串拼接的坑。核心实现有 4 种QueryWrapper基础条件构造器通过字符串指定字段如eq(name, 张三)LambdaQueryWrapper基于 Lambda 表达式的构造器如eq(User::getName, 张三)推荐优先使用UpdateWrapper/LambdaUpdateWrapper用于构建更新条件支持动态设置 set 值。为什么优先用 LambdaQueryWrapper对比传统写法的优势一目了然// ❌ 不推荐字段拼写错误编译不报错字段变更易遗漏 QueryWrapperUser queryWrapper new QueryWrapper(); queryWrapper.eq(name, 张三).gt(age, 18); // ✅ 推荐编译期检查字段有效性重构自动更新引用 LambdaQueryWrapperUser lambdaQueryWrapper new LambdaQueryWrapper(); lambdaQueryWrapper.eq(User::getName, 张三).gt(User::getAge, 18);优势体现在防止字段拼写错误导致的 SQL 异常编译期校验自动校验值类型避免因类型不匹配导致索引失效提高代码可读性字段含义一目了然。2.3 丰富的插件集合功能扩展的 万能接口MyBatis-Plus 通过MybatisPlusInterceptor实现插件机制可拦截 SQL 执行过程并增强功能核心插件如下插件名称作用关键说明PaginationInnerInterceptor自动分页必须配置否则会内存分页不拼接 limitBlockAttackInnerInterceptor防止全表更新 / 删除拦截不带 WHERE 条件的 update/delete避免误操作OptimisticLockerInnerInterceptor乐观锁通过版本号字段解决并发更新冲突TenantLineInnerInterceptor多租户自动为 SQL 添加租户 ID 条件隔离数据IllegalSQLInnerInterceptorSQL 性能规范拦截不符合规范的 SQL如 SELECT *3、使用建议写出高效、安全的 MP 代码3.1 优先使用 Lambda 条件构造器再强调一次别用字符串拼条件LambdaQueryWrapper能在编译期帮你挡住 字段不存在、类型不匹配 等一堆坑重构时还能自动更新引用 —— 相当于给代码加了 自动纠错buff。3.2 条件构造器 NULL 值处理减少冗余代码当查询条件含null值时传统写法需用if判断避免无效条件MP 支持 条件性添加一行代码搞定// ❌ 不推荐大量if判断 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); if (StringUtils.isNotBlank(name)) { wrapper.eq(User::getName, name); } if (age ! null) { wrapper.eq(User::getAge, age); } // ✅ 推荐条件性添加减少冗余 wrapper.eq(StringUtils.isNotBlank(name), User::getName, name) .eq(Objects.nonNull(age), User::getAge, age);优势减少 if 判断、避免无效查询条件。3.3 尽量明确 select 字段提升查询效率默认情况下selectList会查询所有字段select *指定查询字段可利用索引覆盖、减少数据传输// ❌ 不推荐全表字段查询浪费资源 ListUser users1 userMapper.selectList(lambdaQueryWrapper); // ✅ 推荐只查询需要的字段 lambdaQueryWrapper.select(User::getId, User::getName, User::getAge); ListUser users2 userMapper.selectList(lambdaQueryWrapper);优势体现在利用索引覆盖避免回表查询提升 SQL 效率减少数据传输和序列化开销降低数据库压力节省内存尤其对大表查询效果明显。4、开发实战从 能用 到 用好 的进阶重点在实际项目中尤其是微服务架构下需要将dao层作为单独的服务对外提供原子能力此时必须解决QueryWrapper在 RPC 场景的痛点同时实现高效接入、功能扩展及安全保障。4.1 基础能力微服务下的查询条件封装4.1.1 痛点与解决方案在微服务中若将 DAO 层封装为独立服务直接暴露QueryWrapper存在 3 大问题QueryWrapper结构复杂序列化 / 反序列化耗时逻辑层需引入mybatis-plus-core依赖易导致 Jar 包冲突原子层升级 MP 版本时所有调用方需同步升级维护成本高。解决方案自定义QueryCondition替代QueryWrapper作为 RPC 入参兼顾灵活性与轻量性。4.1.2 QueryCondition 设计QueryCondition整合查询、排序、分页及字段选择能力结构如下查询条件集合queryFieldList封装 WHERE 子句的条件包含字段名、匹配规则如等于、模糊查询、拼接方式AND/OR排序字段集合orderFieldList定义排序字段及排序方式ASC/DESC返回字段集合selectFieldList指定查询结果需返回的字段避免select *分页参数页码pageNum和每页条数pageSize。// 综合查询条件类 public class QueryCondition { private ListString selectFieldList; // 返回字段 private ListQueryField queryFieldList; // 查询条件 private ListOrderField orderFieldList; // 排序字段 private int pageNum; // 页码 private int pageSize; // 每页条数 }4.1.3 类型安全的构建工具QueryConditionBuilder为简化QueryCondition的创建设计QueryConditionBuilder工具类通过 Lambda 表达式实现类型安全构建public class QueryConditionBuilderT { private ListSelectFieldBuilderT selectFieldBuilderList; // 返回字段构建 private ListQueryFieldBuilderT queryFieldBuilderList; // 查询条件构建 private ListOrderFieldBuilderT orderFieldBuilderList; // 排序字段构建 }使用示例Test public void selectByQuery() { QueryConditionBuilderUser builder QueryConditionBuilder.builder(); QueryCondition condition builder .select(User::getId, User::getName) // 选择返回字段 .eq(User::getStatus, 1) // 相等查询 .like(User::getName, 张) // 模糊查询 .in(User::getType, Arrays.asList(1, 2, 3)) // 集合查询 .orderByDesc(User::getCreateTime) // 排序 .pageNum(1) // 页码 .pageSize(5) // 每页条数 .build(); // 构建时校验数据类型不匹配则抛异常 }4.1.4 接口与实现设计接口定义在dao-contract中定义通用接口BaseDao对外暴露单表操作的CRUD能力public interface BaseDaoP extends Serializable, T { Master T insert(T entity, Option... option); // 新增 Slave ListT selectListByQuery(QueryCondition queryCondition, Option... option); // 条件查询 // 其他方法... }实现设计在dao-service通过抽象类AbstractBaseDaoImpl继承 MyBatis-Plus 的ServiceImpl并实现BaseDao接口在抽象类中将自定义的QueryCondition转换成LambdaQueryWrapper再调用ServiceImpl中的方法实现BaseDao中对外暴漏的所有方法public abstract class AbstractBaseDaoImplP extends Serializable, T, M extends BaseMapperT extends ServiceImplM, T implements BaseDaoP, T { Override public T insert(T entity, Option... option) { // 适配自定义配置项 this.beforeOption(option); try { if (Objects.nonNull(entity)) { super.save(entity); } return entity; } finally { // 回滚自定义配置项 this.afterOption(option); } } Override public ListT selectListByQuery(QueryCondition queryCondition, Option... option) { if (Objects.isNull(queryCondition)) { return new ArrayList(); } this.beforeOption(option); try { final WrapperT queryWrapper this.queryCondition2QueryWrapper(queryCondition); return super.list(queryWrapper); } finally { this.afterOption(option); } } // ...... 其他方法的实现 }4.1.5 整体架构采用分层架构实现高内聚低耦合kf_scaffold/ ├── dao-contract/ # 接口定义层暴露对外RPC接口 ├── dao-service/ # 服务实现层实现接口依赖MyBatis-Plus ├── dao-plugin/ # 插件扩展层自定义插件如全表拦截 ├── dao-spring-boot-starter/ # 自动配置层封装Starter简化接入 └── dao-demo/ # 使用示例层提供接入示例4.2 快速接入3 步实现单表 CRUD 接口以售后单表AssOrder为例快速搭建对外暴露的 CRUD 服务4.2.1 接口层工程ass-dao-contract引入依赖dependency groupIdcom.bj58.zhuanzhuan.kf/groupId artifactIddao-contract/artifactId /dependency定义实体// 售后单据 public class AssOrderEntity { private Long id; // 售后单ID // 其他字段... }定义接口继承BaseDao无需编写方法实现// 售后单表的 CRUD 接口 public interface IAssOrderDao extends BaseDaoLong, AssOrderEntity { }4.2.2 服务层工程ass-dao-service引入依赖dependency groupIdcom.bj58.zhuanzhuan.kf/groupId artifactIddao-spring-boot-starter/artifactId /dependency配置数据源区分主从库实现读写分离kf: dao: data-source: master: url: jdbc:mysql://localhost:3306/master_db driver-class-name: com.mysql.cj.jdbc.Driver slave: url: jdbc:mysql://localhost:3306/slave_db driver-class-name: com.mysql.cj.jdbc.Driver编写实现继承AbstractBaseDaoImpl无需手动实现方法// 售后单表的 CRUD 接口实现 public class AssOrderDao extends AbstractBaseDaoImplLong, AssOrderEntity, AssOrderMapper implements IAssOrderDao { }效果通过上述步骤无需编写 SQL即可对外提供AssOrder表的 CRUD 接口支持通过QueryCondition进行条件查询、排序、分页等操作。4.3 丰富 BaseMapper扩展自定义通用方法BaseMapper的默认方法若不满足需求如按实体属性统计数量可按以下步骤扩展4.3.1 自定义 Mapper 接口定义MyMapper继承BaseMapper添加自定义方法public interface MyMapperT extends BaseMapperT { // 按实体属性拼接AND条件统计数量 int countByEntity(T entity); }4.3.2 注入方法实现通过AbstractMethod构建 SQL 模板实现countByEntity的逻辑public class CountByEntityMethod extends AbstractMethod { private static final String SQL_TEMPLATE script%s SELECT COUNT(%s) FROM %s %s %s\n/script; Override public MappedStatement injectMappedStatement(Class? mapperClass, Class? modelClass, TableInfo tableInfo) { String sql String.format(SQL_TEMPLATE, sqlFirst(), // 前置SQL selectColumns(tableInfo, true), // 计数字段 tableInfo.getTableName(), // 表名 sqlWhereEntityWrapper(true, tableInfo), // WHERE条件基于实体属性 sqlComment()); // 注释 SqlSource sqlSource languageDriver.createSqlSource(configuration, sql, modelClass); return addSelectMappedStatementForOther(mapperClass, countByEntity, sqlSource, Integer.class); } }4.3.3 注册自定义方法通过SqlInjector将自定义方法注入 MyBatis-Plus// 自定义注入器 public class MySqlInjector extends DefaultSqlInjector { Override public ListAbstractMethod getMethodList(Class? mapperClass, TableInfo tableInfo) { ListAbstractMethod methods super.getMethodList(mapperClass, tableInfo); methods.add(new CountByEntityMethod()); // 添加自定义方法 return methods; } } // 配置注入器,将自定义注入器放入spring环境中 Configuration publicclass MybatisPlusConfig { Bean public MySqlInjector customSqlInjector() { return new MySqlInjector(); } }4.3.4 使用扩展方法// 定义 UserMapper 继承自定义的 MyMapper Mapper public interface UserMapper extends MyMapperUser { } // 调用示例 Test public void testCountByEntity() { User user new User(); user.setName(张三); // 按姓名统计 int count userMapper.countByEntity(user); System.out.println(符合条件的用户数 count); }4.4 内置插件增强系统安全性为避免生产环境中的误操作在dao-plugin工程中定义了两个插件4.4.1 全表扫描拦截FullTableScanInterceptor功能拦截无查询条件的 SQL如select * from user防止全表扫描导致的性能问题场景当QueryCondition未设置查询条件时自动拦截并抛异常。4.4.2 全表更新拦截BlockFullTableOperationInterceptor功能拦截无更新条件的 SQL如update user set status0防止全表更新价值避免因条件构造器错误导致的批量数据修改从源头降低风险。5、总结为什么 MyBatis-Plus 值得用MyBatis-Plus 的核心价值在于用最小的改造成本实现 DAO 层开发效率的质的飞跃。对开发者减少 90% 的 CRUD 代码用 Lambda 替代字符串拼接从 写 SQL 转向 拼条件对系统通过插件机制增强安全性防删库、SQL 规范和可扩展性分页、多租户对团队降低新人上手成本统一 DAO 层编码规范减少因 SQL 问题导致的线上故障。