
代码生成完跑不起来才是常态一次完整的排错记录那天下午四点代码全部生成完毕IDE 里一片飘绿没有一个红色波浪线。我端起杯子准备庆祝然后mvn spring-boot:run一敲控制台开始刷血。先交代一下背景。上一篇我把课程管理后台的分层代码逐层拆开看了一遍改了四个半小时自认为改得挺扎实。改完之后我心里是有底气的结构规整、注解齐全、SQL 也在 Navicat 里跑通了。跑不起来不可能。然后现实给了我一巴掌。从代码生成完到接口真的返回 200中间隔了整整一天半我踩了八个坑。这篇文章把这八个坑按发生顺序完整记下来每个坑都按「报错原文 → 原因 → 解决办法」三段写。不是为了凑字数是因为我后来发现这些坑里至少有五个是每次都会重复出现的跟项目无关跟人有关。你要是也在用飞算JavaAI 的/后端开发指令生成代码建议先收藏等报错的时候按第十章那张顺序表从上往下查能省不少时间。一、第一个拦路虎依赖版本冲突mvn spring-boot:run敲下去编译通过了启动到一半炸了。Caused by: java.lang.NoSuchMethodError: org.mybatis.spring.boot.autoconfigure.MybatisPlusAutoConfiguration .sqlSessionFactory(Lorg/apache/ibatis/session/SqlSessionFactory;)V at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory .initializeBean(AbstractAutowireCapableBeanFactory.java:1806) ... 2026-09-15 16:07:23.441 ERROR 14392 --- [main] o.s.boot.SpringApplication : Application run failed原因NoSuchMethodError我见得太多了一看见就知道是版本对不上。打开pom.xml一看MyBatis-Plus 用的是 3.5.x但配套的mybatis-spring被另一个依赖间接带成了老版本。Maven 的依赖调解规则是就近优先谁离得近用谁的结果低版本把高版本顶掉了。方法签名对不上JVM 在运行时才发现。这个坑的恶心之处在于编译期完全不报错。IDE 里代码提示也正常因为编译用的是高版本的 jar。只有运行时才炸。解决办法先定位冲突来源。mvn dependency:tree-Dverbose|grepmybatis找到那个带低版本的依赖之后有两种处理方式。我选了第一种直接锁版本dependencyManagementdependenciesdependencygroupIdorg.mybatis/groupIdartifactIdmybatis-spring/artifactIdversion3.0.3/version/dependencydependencygroupIdorg.mybatis/groupIdartifactIdmybatis/artifactIdversion3.5.16/version/dependency/dependencies/dependencyManagement第二种是排除法在引入的那个依赖里exclusions掉传递依赖。我一般只在冲突来源很明确时用排除法否则dependencyManagement锁版本更省心后续加依赖也不容易再踩。另外提醒一句MyBatis-Plus 的版本要和 Spring Boot 版本对得上。我用 Spring Boot 2.7 配的是mybatis-plus-boot-starter3.5.x如果你上 Spring Boot 3.xstarter 的坐标和包名都变了mybatis-plus-spring-boot3-starter照抄老配置会直接起不来。二、数据库连不上三种报错三种原因依赖问题解决后重新构建这次启动到数据源初始化就挂了。我前后遇到三种不同的报错挨个说。2.1 服务根本没起来com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.原因MySQL 服务没开。我本机装的 MySQL 设成了手动启动重启电脑之后就忘了。解决办法这个最没技术含量但排查价值高——遇到连接失败先别看配置先确认服务活着。# Windows 下net start mysql80# 或者先探一下端口telnet127.0.0.13306如果端口不通改配置是没用的。我在这上面浪费了十几分钟一直在检查用户名密码。2.2 数据库不存在java.sql.SQLSyntaxErrorException: Unknown database course_db原因application.yml里写的库名course_db但我本地建的是course。生成代码时数据库设计文档里用的是一个名字我建库时手快打了另一个。解决办法改配置或者建库我选了建库因为接口文档里其他同学也在用course_db这个名字。spring:datasource:driver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql://127.0.0.1:3306/course_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrueusername:rootpassword:roottype:com.zaxxer.hikari.HikariDataSourceserverTimezone这个参数必须带不然 MySQL 8 会报时区错误。我见过太多人卡在这里报错信息还特别误导人说的是别的问题。2.3 账号密码对了还是被拒java.sql.SQLException: Access denied for user rootlocalhost (using password: YES)原因MySQL 8 默认的认证插件是caching_sha2_password某些老版本的驱动不支持。另外也可能是 root 用户只允许localhost登录而你用的是127.0.0.1——MySQL 眼里这俩不是一回事。解决办法要么升级驱动到 8.0.x要么改认证方式ALTERUSERroot%IDENTIFIEDWITHmysql_native_passwordBYroot;FLUSHPRIVILEGES;我强烈建议不要直接用 root建一个专用账号。本地开发无所谓但这套代码要上测试环境用 root 的习惯会跟着你走迟早出事。三、MyBatis 映射报错Invalid bound statement数据库通了调第一个列表接口500。org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.example.course.mapper.CourseMapper.selectCourseList at org.apache.ibatis.binding.MapperMethod$SqlCommand.init(MapperMethod.java:235) at org.apache.ibatis.binding.MapperProxy.lambda$cachedInvoker$0(MapperProxy.java:115)原因这个报错是 MyBatis 的经典入门题含义很直白——接口方法找不到对应的 SQL 语句。我查下来是配置文件里的扫描路径写错了mapper-locations写成了classpath:/mapper/*.xml但我的 XML 放在resources/mapper/course/下面单层通配符匹配不到子目录。解决办法用双星号递归匹配并且加上classpath*:前缀。mybatis-plus:mapper-locations:classpath*:/mapper/**/*.xml改完还是报同样的错那按这个顺序继续排查这四步能覆盖九成情况XML 的namespace是不是 Mapper 接口的全限定名一个字符都不能差。XML 里select的id是不是和方法名完全一致大小写敏感。XML 文件是不是真的被打进 target 目录了。Maven 默认只打包resources下的资源如果你把 XML 放在java目录里需要在 pom 里额外配置resources。target/classes里有没有旧的同名文件。这个我栽过改了 XML 但编译产物没更新mvn clean一下就好了。顺便说一句开启 SQL 日志能救命mybatis-plus:configuration:log-impl:org.apache.ibatis.logging.stdout.StdOutImpl生产环境千万别开本地调试时开着你能直接看到最终执行的 SQL 和参数比任何猜测都准。四、字段对不上Unknown column 和驼峰映射通了SQL 出来了但报了个新错。### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column categoryId in field list ### SQL: SELECT id,title,categoryId,teacherId,status FROM course WHERE deleted 0原因看到 SQL 我就笑了。categoryId这名字数据库里根本没有真实字段是category_id。问题出在实体类——我在补 Entity 的时候为了和 DTO 保持一致把字段名写成了驼峰同时又没开启驼峰映射转换MyBatis 就把驼峰名原样拼进 SQL 了。解决办法两个方向选一个别两个都做会打架。方向一开启下划线转驼峰mybatis-plus:configuration:map-underscore-to-camel-case:true方向二实体字段保持驼峰但在 XML 里用AS显式转成驼峰别名。我一般用方向一MyBatis-Plus 默认是开启的只要你不手贱关掉就没这事。这次我还顺带发现另一个字段问题逻辑删除字段没生效。列表里把已删除的课程也查出来了。原因是我在 Entity 上加了TableLogic但 XML 里手写的 SQL 没带deleted 0条件——TableLogic只对 MyBatis-Plus 自动生成的 CRUD 生效你自己写的 XML 它管不着。wherec.deleted 0iftestquery.title ! null and query.title ! AND c.title LIKE CONCAT(%, #{query.title}, %)/if/where这个坑很隐蔽因为不报错只是数据不对。我是在列表里看到一条明明删掉的课程还在才反应过来。凡是自己写的 XML逻辑删除条件必须手动加。五、事务不生效不报错但数据脏了这个坑我卡了快两个小时因为它没有任何报错。现象是这样的我写了个测试让changeStatus在更新课程状态之后、写操作日志之前故意抛异常然后看数据库——课程状态已经改了日志表里空空如也。事务完全没回滚。按照上一篇文章说的Transactional(rollbackFor Exception.class)我加了注解在实现类的方法上方法是 public 的。那问题在哪我把排查过程记下来这个顺序是有讲究的数据库引擎是不是 InnoDB。我查了一下SHOW TABLE STATUS一看有一张表的引擎是 MyISAM不支持事务。这是建表 SQL 里漏写ENGINEInnoDB导致的。ALTERTABLEcourseENGINEInnoDB;异常是不是被吞了。我打断点跟了一遍发现生成的代码里有一处 try-catch 把异常吃掉了只打了日志。事务感知不到异常直接提交。// 错误写法异常被吞事务照常提交OverrideTransactional(rollbackForException.class)publicvoidchangeStatus(Longid,CourseStatusDTOdto){try{courseMapper.updateById(update);courseLogMapper.insert(log);}catch(Exceptione){log.error(更新失败,e);}}// 正确写法异常往上抛交给全局异常处理器OverrideTransactional(rollbackForException.class)publicvoidchangeStatus(Longid,CourseStatusDTOdto){courseMapper.updateById(update);courseLogMapper.insert(log);}是不是同类内部调用。这个我上一篇文章提过this.xxx()不走代理事务静默失效。我全局搜了一遍this.还好没有。Spring 有没有扫到这个类。如果 Service 类没被注册成 BeanTransactional就是个普通注解。我在启动日志里搜CourseServiceImpl确认被加载了。最后定位下来是前两个原因叠加一张表是 MyISAM另一处的异常被吞了。改完之后再测回滚正常。我的经验是事务问题别靠猜写个必然抛异常的测试用例看数据库。这比看十遍代码都管用。六、接口 404路径对了但就是访问不到前端联调的时候同事说列表接口 404。我在浏览器直接敲也是 404。{ timestamp: 2026-09-16T10:22:31.51700:00, status: 404, error: Not Found, path: /api/course/page }原因Controller 明明有GetMapping(/page)类上也有RequestMapping(/api/course)。我一开始以为是路径写错了比对半天没问题。后来才发现是包扫描路径的问题——CourseApplication启动类放在com.example下面而 Controller 在com.example.course.controller。按理说com.example是父包应该能扫到才对。真正的原因是我在测试时临时加了一个ComponentScan把扫描范围限定死了自己忘了删。这种事说出来挺丢人的但确实是真实发生的。解决办法删掉那段多余的ComponentScan让 Spring Boot 用默认的扫描规则启动类所在包及其子包。顺便检查两件事Controller 上有没有RestController。只写Controller的话返回的会被当成视图名解析表现也是 404 或者 500。有没有配server.servlet.context-path。如果配了所有路径前面都要多一段前缀前端最容易在这里栽跟头。server:port:8080servlet:context-path:/course-admin配了上面这段访问地址就变成/course-admin/api/course/page了。我建议本地开发不要配 context-path能少一层不确定性。排查 404 最快的一招启动日志里搜Mapped Spring Boot 启动时会把所有注册进来的映射打出来一眼就能看到你的接口在不在。2026-09-16 10:25:03.112 INFO 8892 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped {[/api/course/page],methods[GET]} onto public com.example.common.Result ...七、接口 500全局异常处理没接住404 解决之后接口能进来了但传个空标题进去直接 500。按我的设计参数校验失败应该返回 400 带上错误信息。2026-09-16 10:41:07.883 ERROR 8892 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors Field error in object courseCreateDTO on field title: rejected value [null]; default message [课程标题不能为空]] with root cause原因异常确实被抛出来了BindException但我的GlobalExceptionHandler里只处理了MethodArgumentNotValidException和BizException没有处理BindException。结果它就一路往上冒被 Spring 默认的错误处理兜底返回了 500。这两者的区别很烦人RequestBody触发的是MethodArgumentNotValidException表单提交或者 GET 参数绑定触发的是BindException。生成代码时通常只覆盖了前者。解决办法两个都接上顺便把漏网的都补了。RestControllerAdviceSlf4jpublicclassGlobalExceptionHandler{ExceptionHandler(MethodArgumentNotValidException.class)publicResultVoidhandleValid(MethodArgumentNotValidExceptione){Stringmsge.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(; ));returnResult.fail(400,msg);}ExceptionHandler(BindException.class)publicResultVoidhandleBind(BindExceptione){Stringmsge.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(; ));returnResult.fail(400,msg);}ExceptionHandler(BizException.class)publicResultVoidhandleBiz(BizExceptione){returnResult.fail(400,e.getMessage());}ExceptionHandler(Exception.class)publicResultVoidhandleException(Exceptione){log.error(系统异常,e);returnResult.fail(500,系统繁忙请稍后重试);}}改完之后前端拿到的是固定的{code, message, data}结构不用再对着 500 的 HTML 错误页发呆。这里有个我坚持的原则对外永远不要暴露堆栈信息。handleException里只回一句系统繁忙详细信息全进日志。日志里带上 traceId出问题能反查就够了。八、跨域最后一道坎后端自己用 curl 测都正常前端一调就红。Access to XMLHttpRequest at http://localhost:8080/api/course/page from origin http://localhost:5173 has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource.原因浏览器的同源策略。前端跑在 5173后端在 8080端口不同就是跨域。这个报错只在浏览器控制台出现后端日志里干干净净所以很容易误判成后端的问题。解决办法后端加 CORS 配置。注意别用allowedOrigins(*)配allowCredentials(true)这俩组合会被浏览器直接拒绝。ConfigurationpublicclassCorsConfig{BeanpublicCorsFiltercorsFilter(){CorsConfigurationconfignewCorsConfiguration();config.addAllowedOriginPattern(*);config.addAllowedHeader(*);config.addAllowedMethod(*);config.setAllowCredentials(true);config.setMaxAge(3600L);UrlBasedCorsConfigurationSourcesourcenewUrlBasedCorsConfigurationSource();source.registerCorsConfiguration(/**,config);returnnewCorsFilter(source);}}还有个更省事的方案前端开发服务器配代理让请求从同源发出去根本不产生跨域。Vue 项目在vite.config.ts里加server.proxy就行。我最终两个都配了——本地开发走代理省事测试环境靠后端 CORS 兜底。有个细节值得记一下如果接口返回 500浏览器看到的报错可能也是 CORS因为错误响应里没带 CORS 头。跨域报错不等于跨域问题先确认接口本身是不是正常返回的。我在这里绕了一圈最后发现是参数校验失败的 500 被浏览器包装成了 CORS 错误。九、这些坑为什么反复出现八个坑排完我回头看了眼发现它们有个共同特征全都是环境配置和框架约定的问题没有一个是业务逻辑问题。生成的代码在业务表达上是过关的——该查的查了该更新的更新了接口路径和参数也符合设计文档。但环境是它看不见的你的 MySQL 是什么版本、你的 Maven 仓库里有哪些传递依赖、你的前端跑在哪个端口、你的表引擎是 InnoDB 还是 MyISAM。这些东西不在设计文档里它就无从知道。想明白这一点之后我的心态变了。以前遇到报错我会烦躁觉得这代码质量不行现在我会先看是环境配置还是逻辑错误前者按流程查后者才是真正需要动脑子的。十、排错顺序表下面这张表是我这次排完总结的顺序。从上往下查不要跳因为下面的问题往往是由上面的问题衍生出来的假象。顺序排查项典型症状我这次耗时1Maven 依赖树NoSuchMethodError、ClassNotFoundException25 分钟2MySQL 服务与端口Communications link failure15 分钟3数据源配置Unknown database、Access denied20 分钟4Mapper XML 扫描Invalid bound statement (not found)30 分钟5字段映射与逻辑删除Unknown column、查出已删数据40 分钟6表引擎与事务不报错数据不回滚2 小时7请求映射注册40420 分钟8全局异常处理器500、返回结构不一致30 分钟9跨域配置浏览器报 CORScurl 正常25 分钟几个能加速排查的小习惯顺手记在这里mvn clean之后再启动。改了 XML 或配置没生效八成是编译产物没更新第一反应就应该是 clean。开 SQL 日志。log-impl: StdOutImpl一开最终 SQL 和参数直接打印比猜快十倍。启动日志搜Mapped 。确认接口到底有没有注册进来比看代码可靠。curl 测一遍再让前端调。curl 不通是后端问题curl 通而浏览器不通才是跨域。写个必然失败的用例验事务。事务问题靠看代码看不出来必须让数据库告诉你。写在最后代码生成出来那一刻的飘绿是幻觉。真正能跑起来得靠你一个坑一个坑填过去。这活儿一点都不酷甚至有点枯燥但它是你在这条链路里唯一没法外包出去的部分——工具可以替你写 Controller但替不了你知道这台机器上 MySQL 到底起没起来。说句更难听的如果哪天生成的代码一次就跑起来了那你该担心的不是这个项目而是你自己在这件事上还剩多少不可替代性。作者简介5 年 Java 后端正在往全栈挪。相信工具能放大人的能力但放大的是你本来就有的东西。