尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Mybatis-Plus条件构造器、自定义SQL与Service接口核心用法实战解析
SpringCloud 系列教程微服务的未来二Mybatis-Plus的条件构造器、自定义SQL、Service接口基本用法做微服务开发跟数据库打交道是绕不开的日常。Spring Cloud 体系里服务拆分之后每个服务都要管好自己的数据Mybatis-Plus 在这套架构里能帮我们省掉大量重复的 CRUD 活儿这一点用过的人基本都回不去了。今天这篇我结合自己在实际项目里的踩坑经验专门把 Mybatis-Plus 三个最重要的基础能力讲透条件构造器、自定义 SQL 和 Service 接口的基本用法。这三个能力如果你只停留在“会用”层面那还远远不够搞清楚它们背后的设计思路和边界你才能在微服务这种复杂的业务场景里真正写出靠谱、可维护的代码。先说清楚这篇适合谁。如果你已经在团队里用 Mybatis-Plus 做过简单增删改查但总觉得有些复杂查询写起来别扭或者看到网上那种超长 QueryWrapper 就头大那这篇内容能帮你把思路理一遍。如果你是刚开始接触这个框架也建议先把自己手头的启动项目跑通再来读这篇不然一些细节你可能没有实际体验。1. 内容整体设计与思路拆解1.1 为什么在微服务架构下选择 Mybatis-Plus微服务架构下每个服务的数据访问层都应该足够内聚、足够轻量。传统 Mybatis 的方式是写 XML 映射文件管理每个 SQL 语句自由度很高但工作量也大尤其是在表结构变化频繁的业务早期改动一个字段可能要同步改实体、改 XML、改接口三个地方漏掉一个就跑不起来。Mybatis-Plus 的出现就是为了解决这部分“机械性”劳动它把单表 CRUD 直接内置到了 BaseMapper 里开发的日常从“写 SQL”变成了“组装条件”。当然不是所有场景都适合用 Mybatis-Plus。它的强项在于单表操作复杂多表关联、复杂子查询、存储过程这些它并不擅长需要配合自定义 SQL 来做。我见过有些团队为了省事硬是用 QueryWrapper 去拼多表关联结果 SQL 查出来慢得离谱而且条件一多就很难阅读这种用法就是典型的设计思路没绕过来。1.2 条件构造器、自定义 SQL、Service 接口三者之间的关系这三个能力在 Mybatis-Plus 里不是孤立的。条件构造器Wrapper负责“拼条件”自定义 SQL 负责“写复杂查询的骨架”Service 接口负责“批量操作和业务逻辑编排”。你可以把 Wrapper 理解成动态生成 where 部分的工具把自定义 SQL 理解成固定 SQL 模板加动态条件下的注入点把 Service 接口理解成服务层和持久层之间的桥梁。这三者搭配使用才能在微服务场景下写出既灵活又有掌控感的代码。我举个实际项目里的例子。假设要做一个订单查询功能需要按订单状态、时间范围、客户名模糊搜索分页查询同时还要联查客户表里的手机号。这个时候BaseMapper 自带的 selectList 显然不够因为你涉及了 join。但如果你直接在 XML 里把 where 条件写死那动态搜索条件就又得靠if标签去拼XML 会越写越长。Mybatis-Plus 的做法是固定 SQL 部分用 XML 或注解写清楚动态条件通过 Wrapper 参数传入既保留了 SQL 的可读性又让查询条件的拼装变得灵活可控。这个设计其实就是把“变化的部分”和“不变的部分”做了良好分离。理解这个思路你在实际设计接口时就能自然分清楚哪些查询用 Wrapper 直接搞定哪些必须上自定义 SQL哪些需要放到 Service 层去做逻辑编排。2. Mybatis-Plus 条件构造器从入门到精通2.1 条件构造器的核心分类条件构造器有两大派系普通 WrapperQueryWrapper、UpdateWrapper和 Lambda 派系LambdaQueryWrapper、LambdaUpdateWrapper。从名字上就能看出Lambda 派系用 Lambda 表达式指代实体字段。为什么推荐用 Lambda 派系因为普通 QueryWrapper 里你要写数据库字段的字符串比如queryWrapper.eq(user_name, 张三)这个user_name一旦写错编译期根本发现不了运行时报错还算好最怕的是没报错但查出来结果不对。Lambda 写法是queryWrapper.eq(User::getUserName, 张三)字段名写错了编译直接报错IDE 还能帮你点进去看重构的时候字段一改这里也跟着变安全性和可维护性完全不是一个量级。不过 Lambda 写法有一点要注意它依赖实体类上加的TableField注解或者驼峰转下划线的映射规则。如果你的实体字段和数据库字段名不匹配记得在实体类上用注解显式标明否则条件构造器拼出来的 SQL 字段名可能出错。我刚开始用的时候实体里有个字段叫phoneNumber数据库列叫phone_num没加注解直接 Lambda 查询结果报列名不存在。这个细节比起普通 QueryWrapper 那种直接写列名的形式反而更容易踩坑。2.2 条件构造器常用 API 解析先把最常用的方法过一遍。eq是等于ne是不等于gt、ge、lt、le分别是大于、大于等于、小于、小于等于这个记不住也正常用几次就熟了。like是模糊匹配它默认会在参数两边拼接%所以like(name, 张)实际上查的是name LIKE %张%。如果你要查以某字符串开头的用likeRight它只会在右边拼%也就是name LIKE 张%这种写法在走索引方面有一定优势能利用上前缀匹配索引。in和notIn用来处理 IN 条件传进去一个集合就行比如in(id, idList)。between两侧闭区间适合时间段查询注意它包含边界值。isNull、isNotNull处理空值条件。拼装好的条件需要分组时用and(consumer)和or(consumer)这两个方法里面可以再嵌套查询条件。这俩方法我重点提一下因为很多人写复杂条件时直接在链式调用里用or结果 SQL 的括号位置不对查出来的数据完全不对。2.3 条件构造器中 and 和 or 的嵌套坑很多新人看到and和or就直接链式往下写比如想要(A AND B) OR C可能写出来成了A AND B OR C运气好倒没问题运气不好逻辑就错了。Mybatis-Plus 的条件构造器执行顺序和 SQL 一样默认是 AND 优先于 OR所以你要精确控制逻辑优先级必须手动嵌套。正确的姿势是这样wrapper.and(w - w.eq(A).eq(B)).or().eq(C)这样拼出的 SQL 才是(A AND B) OR C。如果你要的是A AND (B OR C)那就这样写wrapper.eq(A).and(w - w.eq(B).or().eq(C))生成的是A AND (B OR C)。记住一个原则只要涉及 OR 且 OR 连着多个条件就要用 and 子查询包一层这个习惯能帮你避掉一大半逻辑坑。还有一点and和or方法里传入的 Consumer 参数它操作的 wrapper 是同一个但要注意不能在内部再调用.and()或.or()去改变外部构建器的拼接逻辑不然条件嵌套会混乱。我通常维护一个原则嵌套逻辑不超过三层超过三层就考虑拆方法或者改自定义 SQL不然可读性基本归零。2.4 条件构造器不能做的三件事条件构造器虽然强大但它不是万能的。第一它不支持动态地从某个表里 select 指定列作为子查询条件虽然有个inSql方法能拼接子查询但传入的是字符串有 SQL 注入风险需要你确保内容是内部常量而不来自前端传参。第二它处理不了 JOIN 场景所有 wrapper 生成的都是单表 SQL。第三它不适合用来做复杂聚合查询比如多级 group by having 窗口函数这类事情你在 wrapper 里拼出来不仅性能差写的人也痛苦。遇到这三种情况果断转自定义 SQL这是一个成熟的开发者该有的判断。3. 自定义 SQL 的正确打开方式3.1 自定义 SQL 的两种实现方式Mybatis-Plus 里自定义 SQL 不外乎两种方式注解方式和 XML 方式。注解方式就是在 Mapper 接口的方法上直接写Select、Update、Delete、Insert。这种方式适合 SQL 语句较短、逻辑固定的场景比如一个简单的查询语句几行能写完加个注解就结束非常轻快。XML 方式就是把 SQL 写到 XML 文件里通过 Mapper 接口方法名映射执行适合 SQL 复杂或经常调整的场景。我个人的经验是超过三行的动态 SQL 尽量放 XML不要堆在注解里。为什么注解里的 SQL 一旦长了在 Java 代码里可读性很差尤其涉及到script动态标签时字符串拼接非常难看而且 IDE 的 SQL 检查在注解里基本帮不上忙。XML 文件里写 SQL 有完善的缩进、注释、高亮还有 Mybatis 专有的if、foreach标签可以用动态 SQL 就该放在这种地方。3.2 自定义 SQL 如何结合条件构造器这是自定义 SQL 最高级的用法。在 Mapper 接口里定义方法时参数列表中加入Param(Constants.WRAPPER)和 Wrapper 参数然后在 SQL 语句末尾用${ew.customSqlSegment}来引用条件构造器拼出来的 SQL 片段。举个例子Select(SELECT * FROM user ${ew.customSqlSegment}) ListUser selectUserByCondition(Param(Constants.WRAPPER) WrapperUser wrapper);这里customSqlSegment输出的是 WHERE 之后的完整条件片段它会自动处理where关键字和条件的拼接逻辑。动态传进来的 wrapper 里有条件它就给 where没条件就直接没有 where完全安全。这个方法最大的好处是固定查询的主干用注解或 XML 自己控制动态条件全部交给调用方去组织对于查询维度特别多的筛选页面这个模式能显著减少 Mapper 层 SQL 数量。使用这种方式有一个必须注意的点${ew.customSqlSegment}用的是字符串拼接不是预编译的#{}占位符。如果你把外部传参直接拼进 wrapper 的字符串形式 eq 方法里存在 SQL 注入风险。好在这个方法官方处理了参数转义条件构造器的值部分仍然是预编译的?占位符所以实际使用是安全的。但你仍需注意不要在自定义 SQL 的${}里直接拼接前端参数那个是真的危险。3.3 自定义 SQL 中分页和排序的处理分页查询是日常开发躲不开的需求。Mybatis-Plus 的乐观锁插件之外还有个分页插件叫PaginationInnerInterceptor配置好之后所有自定义 SQL 只要返回类型是IPage或参数里有IPage它都会自动在 SQL 后面拼 LIMIT。这个插件原理是拦截执行器重写 SQL所以你的自定义 SQL 不需要自己写 LIMIT。不过有一个坑分页插件对复杂 SQL 可能会生成错误的 count 查询。它默认生成SELECT COUNT(*) FROM (你的原SQL) total如果原 SQL 里面有 ORDER BY、聚合函数、某些数据库的复杂语法这个 count 查询可能会错甚至很慢。我的习惯是分页查询里自定义 SQL 尽量只写必要的 join 和 where排序放到 wrapper 的 orderBy 里这样分页插件生成的 count SQL 才会高效正确。如果是特别复杂的报表类 SQLcount 建议自己单独写一个 count 查询方法不要依赖插件的自动 count。还有一个细节分页插件配置时要设置maxLimit参数。我们线上环境就出现过一次事故测试同学在接口里传了一个超大分页值直接把数据库内存撑爆数据库主机 CPU 飙升。后来我配置了 maxLimit 限制最大分页大小为 500配合拦截器直接对超出阈值的分页进行 SQL 改写才彻底解决这个问题。所有分页接口上线前一定要加这个保护。4. Service 接口与 IService 的基本玩法4.1 IService 和 ServiceImpl 的定位IService 是 Mybatis-Plus 提供的 Service 层通用接口它把 Service 层常见的增删改查、批量操作、链式查询都打包好了。配合 ServiceImpl 实现类你只需要让自己的接口继承 IServiceServiceImpl 实现它整个 Service 层基座就搭好了。和 Mapper 层的 BaseMapper 一样IService 也是泛型设计形如IServiceUser你定义业务接口时直接继承就好。很多新手会觉得既然 BaseMapper 已经有了 CRUD为什么还要个 IService这涉及分层设计的问题。Controller 不应该直接操作 Mapper业务逻辑应该沉淀在 Service 层。IService 的批量操作、调用链路封装、事务管理都能在这一层提供统一入口。换句话说BaseMapper 解决的是“单表 SQL 自动生成”IService 解决的是“Service 层模板方法”两者的目的相同——减少机械代码。4.2 Service 接口的三大特色操作IService 里我觉得最值得认真用的是批量操作。默认的批量方法saveBatch和updateBatchById的实现是循环执行单条 SQL性能并不好好在它内部有 batchSize 参数可以控制每批提交多少条。如果你有大量数据的插入需求建议配合ExecutorType.BATCH或者自己拼接多条 insert 语句这样性能才能上来。IService 自带的批量方法方便是方便但别指望它性能很强。第二个特色是链式查询和链式更新。query()返回一个ChainQuery对象可以直接像 QueryWrapper 一样拼接条件并执行查询lambdaQuery()返回的是 Lambda 链式查询比如ListUser users userService.lambdaQuery() .eq(User::getStatus, 1) .like(User::getName, 张) .list();这个写起来很舒服尤其适合 Controller 层直接调用简单查询场景的接口不需要专门定义 Service 方法。但要注意链式查询本质上还是走的 Wrapper多表关联它依然搞不定。链式更新的用法对称lambdaUpdate()支持 set 和条件连用批量更新某个字段非常轻便。第三个特色是getOne方法。IService 里有个getOne(Wrapper)方法但如果查询结果有多条记录getOne默认会报错。它实际上构建了一条带 LIMIT 1 的查询可如果条件结果集大于一条会抛异常。这在业务上其实很有用可以强制保证唯一性约束但前提是你业务上真的期望这一条存在。如果只是想取任意一条比如查商品列表取一个默认配置那建议用getOne(wrapper, false)第二个参数传 false 表示不抛出异常直接返回第一条。这个细节很多人不清楚接口总是无端报错排查半天。4.3 Service 层常用的链式调用和 lambda 写法推荐我个人在实际团队里推过一个约定Service 层的简单查询统一用lambdaQuery()写出来的代码既简洁又能保证字段类型安全。比如用户点击查询自己的订单列表Controller 调 ServiceService 里直接public ListOrder getMyOrders(Long userId) { return this.lambdaQuery() .eq(Order::getUserId, userId) .eq(Order::getStatus, 1) .orderByDesc(Order::getCreateTime) .list(); }这段代码没有多余的 wrapper 对象一眼就能看懂查询条件也不会出现把user_id拼错的情况。至于更新操作lambdaUpdate()提供了一种不用先查出来再改的写法比如用户修改状态public boolean disableUser(Long userId) { return this.lambdaUpdate() .eq(User::getId, userId) .set(User::getStatus, 0) .update(); }update()返回布尔值表示是否执行成功而这里的成功与否不只看受影响行数还包括了更新操作是否与数据库交互正常。如果你的条件命中 0 条数据返回值可能是 false这在业务里要注意区分。比如用户已经不存在但你调用了 disableUser返回 false这时候到底算操作成功还是业务异常需要你自己设计异常处理逻辑。5. 条件构造器和 Service 的配合实战5.1 一个典型业务场景的完整代码把前面的知识点串起来我模拟一个常见的微服务订单管理场景。订单查询接口要支持按用户、状态、日期范围、订单号精确查询还要分页。Controller 层接收参数Service 层构造条件Mapper 层执行查询。但订单查询要联用户表所以这里就用到了自定义 SQL。代码结构大致如下。Mpper 接口定义public interface OrderMapper extends BaseMapperOrder { IPageOrderVO selectOrderPage(PageOrderVO page, Param(Constants.WRAPPER) WrapperOrderVO wrapper); }XML 里写select idselectOrderPage resultTypecom.example.vo.OrderVO SELECT o.id, o.order_no, o.user_id, u.phone_number FROM order o LEFT JOIN user u ON o.user_id u.id ${ew.customSqlSegment} /selectService 层public IPageOrderVO queryOrderPage(OrderQuery query) { PageOrderVO page new Page(query.getPageNum(), query.getPageSize()); LambdaQueryWrapperOrderVO wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(query.getPhone()), OrderVO::getPhone, query.getPhone()) .eq(query.getStatus() ! null, OrderVO::getStatus, query.getStatus()) .apply(StringUtils.hasText(query.getOrderNo()), o.order_no {0}, query.getOrderNo()); return this.baseMapper.selectOrderPage(page, wrapper); }这里注意like方法的第一个参数传了一个布尔值只有条件成立时才拼接这个条件这是 Mybatis-Plus 条件构造器里非常实用的功能把参数判空逻辑直接写到链式调用里省掉了一堆 if。这种写法叫条件判断参数官方也有单独的方法重载支持强烈推荐团队里统一用这种风格。5.2 批量插入与批量更新的注意事项Service 层的saveBatch用起来简单但在高并发微服务环境里要注意数据库连接和事务的粒度。批量插入如果一次性插入几千上万条即使分了批也可能因为事务过长拖垮数据库连接。我在项目中处理大量历史数据导入时一般手动分段提交每批 500 条并控制事务边界Transactional(rollbackFor Exception.class) public void importHistory(ListOrder orderList) { for (int i 0; i orderList.size(); i 500) { ListOrder subList orderList.subList(i, Math.min(i 500, orderList.size())); this.saveBatch(subList, 500); } }注意subList方法返回的是原列表的视图如果你在循环里修改原列表会干扰分批逻辑。实际开发中我更建议用stream配合Collectors.toList复制一份再分批操作。批量更新推荐updateBatchById但它默认每批次提交一次事务在有关联表需要同步的场景务必处理事务的并发问题。比如更新订单主表 status 的同时还要更新明细表的状态这种跨表一致性建议直接操作 Mapper 自定义 SQL不要依赖 Service 的批量方法。5.3 ServiceImpl 自定义方法覆盖内置方法的场景IService 内置方法在大多数场景够用但业务上的一些操作需要你重写默认方法。比如删除操作我写过一套逻辑删除和物理删除混用的方案。系统里大部分表是逻辑删除但日志表需要物理删除。IService 默认的removeById是走逻辑删除的只要实体类上有TableLogic标注。如果我要物理删除就不能用默认方法得自定义public boolean physicallyRemoveById(Long id) { return this.baseMapper.deleteById(id) 0; }这个场景最容易被忽略的是TableLogic注解一旦加上Mybatis-Plus 在所有自动 SQL 里都会自动追加deleted 0条件包括selectById、updateById等。如果你在业务上确实需要查到已逻辑删除的数据那只能走自定义 SQL而且自定义 SQL 里要显式写出不包含逻辑删除条件否则查不到。我遇到过开发同学把TableLogic加在了不该逻辑删除的表上导致线上数据查不到了排查了半天才发现是这个问题。6. 实操过程与核心环节实现6.1 关键配置一览Mybatis-Plus 能跑起来关键配置就这么几样。首先引入依赖Spring Boot 项目里你需要在pom.xml中加dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency版本没有特殊指定时可以根据你 Spring Boot 的大版本选择一个稳定版本。然后配置数据源这部分和普通 Mybatis 完全一样。接着在启动类上添加MapperScan注解告诉框架去哪里扫描 Mapper 接口SpringBootApplication MapperScan(com.example.order.mapper) public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }在application.yml里配置 Mybatis-Plus 的行为通常这几项是关键mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: assign_id logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0map-underscore-to-camel-case决定了下划线和驼峰的自动映射。logic-delete-field指定了逻辑删除字段的全局配置这样实体里标注了TableLogic的字段就会自动生效。id-type: assign_id用的是雪花算法省去了手动设置主键的麻烦在微服务环境里比数据库自增主键更合适因为分布式环境下数据库自增会产生单点瓶颈和跨库唯一性问题。6.2 分页插件注册示例分页插件是需要显式注册的。用Bean方式注册即可Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; } }DbType 根据你的数据库实例填写Maven 依赖里要包含对应的数据库驱动MySQL 就用DbType.MYSQL。分页插件和运行环境强相关如果你有多数据源还需要注意每个数据源的 DbType 要与实际一致否则生成的分页 SQL 可能无法通过语法解析我就见过 PostgreSQL 数据源上配了 DbType.MYSQL结果分页查询直接报错的情况。6.3 实际运行效果与参数验证配置完成后写一个最简单的条件构造器查询开启log-impl: StdOutImpl实际观察打印的 SQL。你会发现 Mybatis-Plus 生成的 SQL 里条件全部用的?占位符预编译参数值单独打印验证了条件构造器安全性。之前我打开日志发现Mybatis-Plus 打印出来的 SQL 是SELECT id,user_name,phone_num FROM user WHERE (user_name LIKE ?)而参数列表里是%张%。这说明逻辑上它把 LIKE 的百分号处理在了参数里而不是拼到 SQL 字符串里这样做索引选择更有利也避免了拼接导致的安全问题。在分页查询验证时日志里你会看到它先执行了一次SELECT COUNT(*)查询再执行SELECT ... LIMIT ?查询。如果你发现 count 语句迟迟慢如牛多半是 join 的表太大或者缺失索引这时你要优化自己写的 SQL 主干而不是优化分页插件因为分页插件不会自动帮你优化这些。7. 常见问题与排查技巧实录7.1 条件构造器查不出数据的排查清单查不出数据的时候先别把锅甩给数据库按照下面的清单逐一排查。第一步确认实体类字段和数据库列名映射是否准确尤其是有没有漏配TableField。第二步检查是不是命中逻辑删除条件了因为逻辑删除字段默认过滤数据可能已被标记删除普通查询当然查不到。第三步查看你自己构造的 Wrapper 条件范围是否有问题比如eq传入了 null 值默认 Mybatis-Plus 的eq不会忽略 null 条件它会生成column null这在 SQL 里永远查不到数据要用eq(boolean condition, column, value)这个重载方法。第四步看一眼 SQL 日志对照日志中的 where 条件人工验证一下逻辑。这套流程走完八成问题都能定位。7.2 Wrapper 爆内存或 SQL 慢的问题有些开发为了图方便一次性查出全表数据再内存过滤这是微服务架构下的反模式。条件构造器本身就是用来在数据库侧完成过滤的不要在内存里二次过滤。如果你发现在接口里用了selectList(null)或者list()无参方法且后续又用 Java 代码做过滤筛选建议立刻改造。数据量小看不出问题百万级数据下全表查询一次就会让服务内存和数据库 IO 同时爆掉。另外一个隐蔽的问题是Wrapper 条件里有函数运算比如apply(DATE_FORMAT(create_time, %Y-%m-%d) {0}, dateStr)这种写法会让日期列上的索引失效全表扫描。如果这种条件是高频率查询建议把日期字段改为时间戳范围比较或者加一个冗余日期字段并建索引能有效避免慢 SQL。7.3 自定义 SQL 与逻辑删除冲突的坑自定义 SQL 默认不会自动拼逻辑删除条件这一点相当容易踩。比如你定义了一个联表查询用Select直接写Select(SELECT * FROM user WHERE name LIKE CONCAT(%, #{name}, %)) ListUser searchUser(String name);如果 user 表有逻辑删除字段这个查询会把已删除的数据也查出来。除非你的业务就是需要查全量历史数据否则必须在 SQL 里手动加AND deleted 0。Mybatis-Plus 的自动逻辑删除只对 BaseMapper 内置方法生效对自定义 SQL 是无效的。要记住这个差异别指望自定义 SQL 也能自动拼接逻辑删除条件。7.4 Service 事务代理失效的两种场景进行微服务开发事务管理非常重要。IService 实现类里直接调用自己的另一个方法比如在methodA里调用this.methodB而methodB加上了Transactional注解此时事务是不生效的。原因是 Spring 的事务是通过 AOP 代理实现的this调用的是当前对象不是代理对象所以注解被忽略了。解决办法注入自身代理或者把methodB拆到另一个 Service 里或者通过 AopContext.currentProxy() 获取代理对象来调用。第二个场景是你在 Service 方法里让异常被 try-catch 吞掉了。事务要回滚依赖方法向外抛出运行时异常你把异常捕获又没重新抛出去Spring 感知不到异常自然不会回滚。这一点在跨服务调用的场景里特别容易出问题事务内的业务处理逻辑里调用了其他服务的接口对方返回失败但被捕获本服务的数据照常提交等到对账发现数据不一致时已经晚了。正确的做法是事务方法的异常一定要传播出去最多在 catch 里先处理本地业务再重新抛出导致回滚的异常类型。7.5 团队分支并发修改代码时的 Mybatis-Plus 版本兼容建议多人在一个服务里协作开发代码冲突在所难免。如果团队里有多个服务依赖 Mybatis-Plus我一般建议统一锁定版本号放在父级pom.xml的dependencyManagement里集中管理。版本升级要谨慎因为 3.4 和 3.5 之间对一些 API 有删改比如分页插件从旧版PaginationInterceptor改成了PaginationInnerInterceptor配合MybatisPlusInterceptor升级时如果是从旧项目迁移不仔细对照就会出现分页失效而报表正常这种奇怪问题。我建议同步再启动测试跑一遍全部接口特别是分页和逻辑删除相关的接口确保行为没有变动。8. 写在最后一口气把 Mybatis-Plus 的条件构造器、自定义 SQL、Service 接口这三块核心能力梳理了一遍这些内容是我在微服务项目里反复演练并踩过不少坑之后总结出来的。实际上把这三个功能吃透日常的单表繁琐操作基本都能削减一大半代码量而遇到真正复杂的业务查询也知道该怎么把自定义 SQL 和动态条件优雅地结合起来。框架工具用得好的关键从来不是背 API而是能判断出什么场景该用哪个能力、哪些操作会带来什么样的副作用这一层理解比多写几行代码重要得多。后面我还会继续分享这套微服务架构系列里的其他主题比如分页查询的性能调优、代码生成器的合理运用、多租户场景的实战方案等等。如果你在实际使用中遇到了和这篇文章相关的问题欢迎带着具体场景来交流说不定你的案例本身就能成为下一篇文章的素材。多折腾、多记录你才能真正把一套框架用出自己的方法论来。
RELATED

相关推荐

Visual Studio一键化C++开发:CMake与vcpkg构建自动化实践

Visual Studio一键化C++开发:CMake与vcpkg构建自动化实践

1. 为什么需要一套“一键化”流程:从手动编译的痛点说起1.1 大多数团队的开发流程到底卡在哪我接手过不少类似 OpenClaw 风格的项目——源码结构不大复杂,但每次从拉取代码到真正跑起来,中间隔着七八个手工步骤:先要安装依赖、配置…

📅 2026/10/17 12:34:40
等保测评方案落地全流程:从定级备案到复测评实操拆解

等保测评方案落地全流程:从定级备案到复测评实操拆解

简介:本资源为等保测评服务方案文档,面向信息安全从业者、系统运维人员及需要开展等级保护工作的企业技术负责人,帮助解决信息系统定级、备案与测评流程缺乏规范参考的问题。压缩包内共1个doc文件,约1.07MB,内容围绕等…

📅 2026/10/17 12:34:40
大数据架构中的数据过滤设计:从存储到计算层的全链路优化

大数据架构中的数据过滤设计:从存储到计算层的全链路优化

数据过滤在大数据架构里,是那种听起来不起眼、实际做起来能把你逼疯的环节。很多刚接触分布式系统的开发者,第一反应是“过滤不就是where条件吗,加个查询不就完了”,但真到了PB级数据、上百台节点一起跑的规模,一个没设…

📅 2026/10/17 12:29:40
MORE NEWS

更多资讯

📰

图即代码:diagram-design 如何构建可维护的架构图设计系统

1. 从一张草图到一套系统:diagram-design 到底在解决什么问题第一次看到 “diagram-design” 这个标题,很多人会以为它只是教你怎么把流程图做得好看一点。但真正在项目里画过架构图、时序图、状态机、数据流图的人都知道,图的美观只是最表层…

📰

C++线性代数库Eigen完全指南:从表达式模板到稀疏矩阵

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

📰

diagram-design:文本优先的架构图设计方法论与可维护工作流

1. 从一张草图到一套系统:diagram-design 到底在解决什么问题第一次看到 “diagram-design” 这个标题,很多人会以为它只是教你怎么画图。但真正做过技术方案、写过设计文档、给团队讲过架构的人都知道,画图这件事背后藏着一整套信息表达系统…

📰

KITTI数据集国内下载与校验避坑指南:从镜像站到目录结构全解析

KITTI数据集,做自动驾驶、三维目标检测、视觉测距、光流估计这些方向的朋友应该都不陌生。它在业界属于标杆级的公开数据集,很多论文都是在它上面做评测,也是很多刚入门同学的第一个“大项目”。但问题来了:这数据集动辄上百GB&am…

📰

diagram-design:用声明式与设计令牌构建可维护的图形系统

1. 从一张草图到一套系统:diagram-design 到底在解决什么问题第一次看到 “diagram-design” 这个标题,很多人会以为它只是教你怎么画图——选个模板、拖几个框、连几条线,导出 PNG 就完事了。但真正在项目里画过架构图、流程图、时序图的人都…

📰

AI数据库不是向量库:从数据模型到事务语义的本质差异

1. 这不是概念炒作,而是技术定位的根本性错位“AI数据库不是向量库”——这句话在2024年中后段的技术圈里,已经从一句争议性断言,变成了不少一线架构师在评审新系统时脱口而出的前置判断。我参与过三个不同行业的AI原生应用落地项目&#xff…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬