【SpringBoot香樟转转】debugDay01
从零搭一个校园二手交易平台,第一天全在跟报错较劲。在做“香樟转转”这个 SpringBoot 项目之前,我其实有过心理准备,但真到了写代码调试的阶段,才发现问题的密度远超预期。这篇文章就把 Day01 这天的 debug 过程完整记录下来,包括版本搭配、IDEA 建项目的坑、自动装配原理、Redis 连接、MyBatis-Plus 日志配置这些内容,希望能给同样在用 SpringBoot 做项目、尤其是卡在 debug 阶段的朋友一点参考。整个项目是 SpringBoot + Vue 前后端分离的校园闲置流转平台,核心功能有用户注册登录、闲置商品发布、商品浏览检索和留言互动,第一天的目标很明确:把项目骨架搭起来,跑通“用户注册 - 登录 - 发布一件闲置商品”这条最小链路。
1. 项目定位与 Day01 目标拆解
1.1 香樟转转到底要做什么
“香樟转转”这个名字源于校园里常见的香樟树,取“闲置流转”的意思,定位是面向高校学生的校内二手交易平台。学生手里有闲置的专业书、小风扇、自行车、宿舍小锅这类东西,扔了可惜,放宿舍占地方,通过“香樟转转”可以快速发布、检索、联系同校的买家,比去综合二手平台更有信任优势。
技术栈选型上我没有太多纠结,SpringBoot 负责后端接口,Vue3 + Element Plus 负责前端页面,MySQL 存业务数据,Redis 做登录态缓存和热点数据缓存,MyBatis-Plus 做数据库操作。选 SpringBoot 的理由很简单,生态成熟、资料多、自动装配能省掉一堆繁琐的 XML 配置,市面上绝大多数中小型项目都在用它,遇到问题基本都能搜到解决方案,不会卡死。
第一天的开发规划是这样的:
- 用 IDEA 创建一个 SpringBoot 工程,确认依赖能正常拉取
- 配置 application.yml,连上本地 MySQL
- 设计 user 表和 goods 表的基础结构
- 写一个注册接口和一个登录接口
- 写一个发布商品的接口
- 用 Postman 完成一次完整链路测试
整个规划看起来不复杂,但实际执行时每一步都踩了坑,下面按时间线拆开细说。
1.2 为什么第一天死磕 debug
很多新手容易犯一个错误,上来就想着把整个系统一口气写完,结果写到最后全是报错,也分不清是哪里出的问题。“香樟转转”第一天我只做最小闭环,就是为了把地基打稳,让后面每一个业务模块都建立在一个跑通的基础上。
debug 本身不是浪费时间,而是项目开发里占比很大的正常环节。我见过太多人遇到报错就慌,要么直接百度复制粘贴一段代码,要么把报错发给 AI 让它猜,自己根本不看错误信息。这种习惯一旦养成,越到后面越难改。第一天的价值就在于用最基础的功能把 debug 的基本节奏跑熟,后面遇到复杂业务才能有条不紊地排查。
2. 环境准备与 IDEA 创建 SpringBoot 项目的关键选择
2.1 SpringBoot 版本和 JDK 版本怎么搭配
开写之前最绕不开的问题就是版本。“springboot版本太高”这个热搜词我太有感触了,网上很多教程是基于 SpringBoot 2.x 写的,你一打开却是 3.x,照着敲都会报错。还有“现在的版本是21,想回退到1.8”这类问题,其实就是 JDK 版本与 SpringBoot 版本之间的兼容性没理清。
先说结论,如果是做类似“香樟转转”这样需要快速上线、以业务为核心的项目,首推稳定组合:SpringBoot 2.7.13 + JDK 8。这套组合的社区资料最多,遇到问题基本能搜到现成答案,各类 Starter 的兼容性也最好。
如果确实想尝鲜,上 SpringBoot 3.x + JDK 17 也可以,但要注意一个关键差异:SpringBoot 3.0 开始把 javax 包名换成了 jakarta,Spring 官方文档和老教程里的很多代码直接复制会编译报错。此外一些第三方 Starter 可能还没适配 3.x,容易卡在依赖兼容上。
| 组合方案 | 适用场景 | 需要留意的点 |
|---|---|---|
| SpringBoot 2.7.x + JDK 8 | 存量项目、毕业设计、大多数业务系统 | javax 命名空间,教程资料最齐全 |
| SpringBoot 3.x + JDK 17 | 新项目、追求新特性 | jakarta 命名空间,部分第三方包需确认是否适配 |
| SpringBoot 2.7.x + JDK 17 | 公司指定版本的情况 | 需要手动确认 starter 兼容性,网上资料较少 |
“香樟转转”我选的是 SpringBoot 2.7.13 + JDK 8,没有为什么,就是求稳。
2.2 创建项目:两种方式与网络问题的处理
创建 SpringBoot 工程有两种常见方式,第一种是用 IDEA 自带的 Spring Initializr,直接在 New Project 里选 Spring Boot 版本和需要引入的依赖;第二种是去 start.spring.io 官网生成压缩包,再导入到 IDEA 里。
实操的时候你大概率会遇到一个问题,IDEA 连不上 Spring Initializr 的服务,一直转圈,最后提示连接超时。这不是电脑坏了,是默认服务地址在国内访问不稳定。解决方法是换成阿里云的镜像地址,把创建项目时的服务 URL 从 start.spring.io 改成 start.aliyun.com。
阿里云镜像创建出来的工程默认用的也是 Maven,建议顺手把 Maven 仓库改成阿里云的 central 镜像,不然 Maven 拉依赖能把你心态拉崩。settings.xml 里 mirror 节点直接加这段:
<mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>依赖下载速度会从“等半天loading”变成“秒下”,这个不改是真的难受。
2.3 单模块起步,别一上来就玩多模块
“香樟转转”的后端工程结构,Day01 我用的是单模块,没有像很多企业项目那样拆分成 common、system、business 多个 Maven 模块。原因是第一天还在验证链路,单模块结构足够清晰,改代码定位问题都快很多。等后面用户体系、商品体系、留言体系都发展壮大了,再按业务边界拆模块也不迟。
工程内部的包结构我按照用户、商品、通用三层划分,没有严格追求 DDD 分层,但保证 Controller、Service、Mapper 各司其职。如果一上来就搞微服务、多模块,光依赖的传递关系和启动顺序就能让新手debug到怀疑人生,这属于把复杂度提前加载了,没有意义。
3. 实战 debug:从启动失败到接口跑通
3.1 启动直接报错:Failed to configure a DataSource
我当时建完工程,兴冲冲写了一个最简单的 Controller,想先启动试试,结果 SpringBoot 启动不到三秒就报错了。核心报错信息是:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.这个报错出现的原因很简单,我在创建项目的时候选了 Spring Web、MyBatis-Plus、MySQL Driver、Redis 这些依赖,其中 MyBatis-Plus 和 MySQL Driver 会让 SpringBoot 在启动时尝试自动配置一个数据源。自动装配机制发现 classpath 里有连接数据库相关的类,却没有读取到任何数据库连接配置,就直接罢工了。
解决方式有两个方向:
方向一:在 application.yml 里配置数据源信息,让自动装配能拿到它需要的东西。我这里用的是 MySQL 8.x,配置如下:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/xiangzhang?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456方向二:如果当前阶段不涉及数据库操作,可以手动排除 DataSourceAutoConfiguration,告诉 SpringBoot“你别自动配数据源了”:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})我在“香樟转转”里用了方向一,因为登录和发布商品都要操作数据库。这里还有一个特别容易踩的坑,注意 url 后面的参数,useSSL 建议设为 false 并指定 serverTimezone,否则会报时区相关的错误或者 SSL 连接警告。第一次配置数据库的人很容易漏掉这些尾部参数,然后又是新一轮 debug。
3.2 写 SQL 没打印:MyBatis-Plus 日志配置
数据源配好之后,项目终于可以启动了。我接着写了一个根据用户名查询用户的 Mapper 接口,测试登录接口时发现一个奇怪的现象:接口能跑通,数据也能查出来,但控制台里看不到任何 SQL 日志。
这个问题看起来小,但影响很麻烦——你无法直观确认 SQL 是否真的经过了某个表、传进去的参数值是什么。在复杂排查场景里,SQL 日志是不可或缺的辅助信息。
MyBatis-Plus 的 SQL 日志需要手动开启,在 application.yml 里加这么一段:
logging: level: com.xiangzhang.mapper: debugcom.xiangzhang.mapper 要替换成你自己项目里 Mapper 接口所在的包路径。配置完成后每次执行数据库操作,控制台都会打印类似这样的语句:
==> Preparing: SELECT id, username, password, nickname, create_time FROM user WHERE username = ? ==> Parameters: xiaochen(String) <== Total: 1有了这个日志,你能清楚看到 MyBatis-Plus 执行的 SQL 和参数绑定,排查“查不到数据”“参数没传进去”这类问题会轻松很多。
3.3 登录接口返回 500,控制台一行 NPE
链路基本打通后,我信心满满地测试登录接口,结果 Postman 直接给你一个红色 500,卡在发送请求的位置。控制台的报错信息如下:
java.lang.NullPointerException: null at com.xiangzhang.service.impl.UserServiceImpl.login(UserServiceImpl.java:38)这个报错信息其实已经很友好了,精准定位到了 UserServiceImpl 的 login 方法第 38 行。我打开代码一看,问题出在用户不存在时,MyBatis-Plus 的 selectOne 方法返回了 null,我却直接调用了 user.getPassword() 去比对密码。
这种问题核心原因是代码里缺少空值判断。修复逻辑很简单,先判断查询结果是否为 null,如果是就抛业务异常,让前端知道“用户不存在”,而不是一个模糊的 500。
User user = userMapper.selectOne( new LambdaQueryWrapper<User>() .eq(User::getUsername, username) ); if (user == null) { throw new BizException("用户名或密码错误"); } if (!BCrypt.checkPassword(rawPassword, user.getPassword())) { throw new BizException("用户名或密码错误"); }这里还有个细节值得提一下,登录失败时的提示信息最好统一为“用户名或密码错误”,不要直接返回“该用户不存在”或“密码错误”,否则别人可以借此探测你的系统里存在哪些用户名。这也是安全实践的一部分。
3.4 Redis 连接报错:Connection refused
登录功能还要配合 Redis 存登录态,我往项目里添加了 Spring Data Redis 依赖,并按默认配置启动后,一调用相关服务就报:
Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost/127.0.0.1:6379我看到这个报错的第一反应是指向 Redis 服务本身,于是在本地命令行执行:
redis-serverRedis 服务启动后再试接口就通了。这个坑属于环境依赖问题,不是代码问题,但在开发阶段很容易被忽略。如果你使用的是云 Redis 或其他远程实例,还需要补充连接配置:
spring: redis: host: 127.0.0.1 port: 6379 password: database: 0 timeout: 5000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0lettuce 连接池参数在高并发场景下非常重要,不加的话默认连接数可能不够,接口压力一大就抛连接异常。Day01 主要验证链路连通性,连接池参数可以先不调,但要知道是干嘛用的。
3.5 用断点调试替代到处打印
上面几个问题都是靠日志和分析解决的,实际开发中更常用的 debug 是断点调试。在 IDEA 中,点击代码左侧行号区域可以打断点,用 Debug 模式启动项目,程序执行到断点位置就会停下来,然后你可以用 F8 步过、F7 步入、F9 跳到下一个断点,实时查看每个变量的值。
我调试“用户不存在导致 NPE”问题时,就是在 UserServiceImpl 的 login 方法打断了点,一步步看 selectOne 返回的值到底是 null 还是一个 User 对象。断点调试为什么重要,因为很多问题不是逻辑复杂,而是程序跑得太快,你根本跟不上数据的流转。断点相当于给程序按下暂停键,给开发者一个观察内存里真实数据的机会。
要注意的是,Debug 模式启动会比正常启动慢一点,打断点也会暂停整个请求线程,线上环境不要用断点调试,否则线程全部挂住会造成严重后果。调试完记得把断点清掉。
4. SpringBoot 自动装配与配置优先级,理解透彻才能少踩坑
4.1 自动装配到底做了什么
“springboot自动装配原理”是被问得最多的面试题,也是日常 debug 绕不开的知识点。“香樟转转”第一次启动报数据源错误,本质上就是自动装配机制在起作用。
SpringBoot 自动装配的核心是 @SpringBootApplication 这个组合注解,它其实是由三个注解拼起来的:
- @SpringBootConfiguration:标记这是一个 Spring Boot 配置类
- @EnableAutoConfiguration:开启自动装配
- @ComponentScan:扫描当前包及其子包下的组件
其中最关键的是 @EnableAutoConfiguration。这个注解会去读取 META-INF/spring.factories 文件,里面罗列了一大批自动配置类,比如 DataSourceAutoConfiguration、RedisAutoConfiguration、JacksonAutoConfiguration 等。SpringBoot 会根据 classpath 中是否存在某个类来判断是否启用对应的自动配置。
举个例子,当 classpath 里出现 com.mysql.cj.jdbc.Driver 和 javax.sql.DataSource 时,DataSourceAutoConfiguration 就会生效,尝试帮助你自动创建数据源 Bean。我的配置里没有指定任何 url,自动配置就会报错。
这就是为什么 3.1 会报 Failed to configure a DataSource。排除自动配置类可以绕过,但真正解决问题的思路应该是:理解自动配置需要哪些前置条件,然后正确补齐这些条件。
4.2 配置文件优先级与常见误区
自动装配帮你做好默认配置,可如果你想要定制行为,就需要通过 application.yml 覆盖。SpringBoot 的配置优先级顺序从高到低大概是:
- 命令行参数
- Java 系统属性
- application-{profile}.yml
- application.yml
- 自动配置类里的默认值
这个优先级在实际开发里很有用,比如本地用 application-dev.yml 连接本地数据库,线上部署时通过命令行参数或者是 application-prod.yml 切换生产环境的配置,不用改动代码就能实现环境切换。
“香樟转转”第一天我就直接建了 application.yml 和 application-dev.yml,开发时用 dev 配置文件,线上部署时用 prod 配置,切换方式是在启动命令里加:
--spring.profiles.active=prod或者直接在 application.yml 里写:
spring: profiles: active: dev配置文件里最容易犯的错是缩进问题,YAML 对缩进非常敏感,一个空格不对,整个配置解析就会失败。而且有些配置项错误不会在启动时立刻暴露,只有调用相关接口时才爆出来。写 YAML 时推荐使用 IDEA 自带的格式化功能,每次都规范缩进,能少很多无谓的 debug 时间。
4.3 常用注解速查与 @ConfigurationProperties
写“香樟转转”的第一批接口时,用到了一批高频注解,我把它们整理成一张速查表,方便对照。
| 注解 | 作用 | 使用位置 |
|---|---|---|
| @RestController | 声明一个控制器并直接返回 JSON | Controller 类上 |
| @RequestMapping | 映射 URL 到处理方法 | Controller 类或方法上 |
| @GetMapping / @PostMapping | 限定 HTTP 方法并映射 URL | Controller 方法上 |
| @RequestBody | 将前端 JSON 自动绑定到 Java 对象 | Controller 方法参数上 |
| @Service | 声明业务层组件 | Service 实现类上 |
| @Mapper / @MapperScan | 注册 MyBatis 的 Mapper 接口 | Mapper 接口或启动类上 |
| @ConfigurationProperties | 绑定配置项到 Java 类 | 配置属性类上 |
| @Autowired / @Resource | 依赖注入 | 需要注入的成员变量或构造器上 |
@ConfigurationProperties 是很多人用得少但很实用的注解。比如前后端分离项目通常有自定义的 JWT 密钥、文件上传路径等配置,散落在业务代码里读取会很乱,不如建一个配置属性类统一管理:
@Component @ConfigurationProperties(prefix = "app.jwt") public class JwtProperties { private String secret; private Long expireSeconds; public String getSecret() { return secret; } public void setSecret(String secret) { this.secret = secret; } public Long getExpireSeconds() { return expireSeconds; } public void setExpireSeconds(Long expireSeconds) { this.expireSeconds = expireSeconds; } }然后在 application.yml 里配置:
app: jwt: secret: xiangzhang-super-secret-key expire-seconds: 86400这样代码里就能用 Spring 注入这个属性类,后续想改配置只需要动 YAML 文件,不用重新编译代码。
5. 常见问题与排查技巧实录
5.1 Day01 高频问题速查表
“香樟转转”Day01 遇到的这些问题,其实都是 SpringBoot 入门阶段的典型问题,我整理成了一张速查表,按“现象 - 原因 - 处理建议”列出,方便你以后快速对照。
| 现象 | 大概率原因 | 处理建议 |
|---|---|---|
| 启动报 Failed to configure a DataSource | 有数据源相关依赖但没有配置连接 | 配置 datasource 或排除自动配置类 |
| 数据库连接时区错误、SSL 警告 | JDBC URL 参数不全 | 加上 serverTimezone、useSSL=false |
| 控制台看不到 SQL 日志 | MyBatis-Plus 日志级别未开 | 配置 logging.level 指定 mapper 包为 debug |
| 登录接口 NPE | selectOne 返回 null 未判空 | 增加空值判断并抛出业务异常 |
| Redis 连接被拒绝 | Redis 服务未启动或配置不对 | 启动 redis-server,检查 host/port |
| 端口 8080 被占用 | 其他进程占用了同端口 | 杀掉进程或改 server.port |
| Maven 依赖下载慢 | 默认中央仓库访问慢 | 换阿里云镜像 |
| YAML 配置生效但取不到值 | 缩进错误或路径名不匹配 | 检查缩进和 key 层级是否和代码一致 |
5.2 Debug 效率提升的三条实用经验
Day01 调试下来,我对 debug 方法论有了更深的体会,分享几条对新手特别实用的经验。
第一条,日志先行。遇到问题先看控制台完整报错,从顶部开始一行行读。很多人只看报错的最下面一行,或者复制一段就去搜,这样效率很低。报错的栈信息里包含了完整的调用链,能直接告诉你问题出在哪个类的哪个方法,这是定位问题的第一手线索。
第二条,小步验证。每写完一个接口,立即启动项目,用 Postman 或者 curl 测试一次。不要攒了一堆代码才一次性启动,一旦报错,你不知道是哪块代码引入的问题,排错范围变大。第一天我就是每完成一个接口就启动一次,虽然启动次数很多,但问题每次都能在一个小范围内被锁定。
第三条,用 Git 做备份点,大胆改代码。很多新手 debug 的时候总是不敢改代码,怕把原本能跑的部分改坏。建议建一个 Git 仓库,每次跑通一个功能就提交一次代码,后面无论怎么改,都能回退到稳定版本。敢于试错,才是 debug 效率提升的关键。
我记得还有一次因为 Maven 本地仓库缓存了旧版本的依赖,代码里明明新增了一个方法,编译却一直报找不到。后来把本地仓库的对应依赖删掉重新拉取,问题才解决。依赖版本不一致导致的诡异问题非常耗费时间,遇到解释不通的现象,可以考虑 clean 一下 ~/.m2 下的缓存文件。
5.3 必须养成的好习惯
写“香樟转转”第一天,项目里每一步我都要求自己保持代码整洁、配置清晰。配置文件尽量分层写,数据库配置、Redis 配置、业务配置分块加注释,这样项目交到别人手里,别人也能快速看懂。给类和方法的命名也要有意义,UserService 就是处理用户逻辑的,GoodsService 就是处理商品逻辑的,不要出现 Test1、Utils2 这类名字。
另外,SpringBoot 社区有一个很好的学习资源“狂神说”系列,我在第一天遇到自动装配概念不清楚时翻了一下他的笔记,确实能帮你把那些零散的知识点串起来。这不是广告,是真心推荐的入门路径。
写在 Day01 结束后
Day01 从清晨搭环境到深夜跑通最小链路,中间踩过的坑应该能算一副扑克牌了。但最有价值的不是“解决了某个报错”,而是掌握了一套 debug 节奏:看报错、查日志、断点跟踪、小步验证。这套节奏在后面的用户模块、商品模块才会真正发挥作用。
如果你也在用 SpringBoot 做项目,或者准备开始做,希望“香樟转转”的 Day01 记录能让你少浪费几小时。Debug 是开发过程中再正常不过的一环,不要烦躁,也不要绕开它,把报错当成产品给你留的提示,逐条处理掉就好。明天开始我会继续记录商品模块和文件上传相关的调试过程,如果对这块感兴趣,可以持续关注这个项目系列。