Spring Boot 多模块只更新一个 JAR,写操作无声失败
手动部署 Spring Boot 多模块项目时踩过最隐蔽的坑之一:在共享实体上加了一个字段,前端提交后响应码 200、页面正常关闭,但打开数据库一看——列还是 NULL。这个坑没有任何报错,整整排查了一个小时。
现象
某后台管理系统采用典型的多模块拆分:module-core(共享实体)、mp(端口 8050,C 端只读 API)、web(端口 8250,管理端写入 API)。在 Activity.java 上新增字段 isShowTitle,手动 mvn package 后只 scp 了 mp 模块的 JAR 并重启。
C 端读取正常,但管理端保存后该字段始终为 NULL,响应却是 HTTP 200,没有任何异常日志。
根因
两个 Spring Boot 进程各自打包了一份 Activity.class。只重新部署 mp 后:
mp进程持有新 class,C 端GET能返回字段。web进程持有旧 class(JAR mtime 仍是上次部署时间),反序列化时触发两个静默行为:- Jackson 默认
FAIL_ON_UNKNOWN_PROPERTIES=false:@RequestBody Activity activity把请求体里的isShowTitle字段视为"未知属性",静默丢弃,不抛异常。 - MyBatis-Plus 默认
FieldStrategy.NOT_NULL:updateById(activity)发现entity.isShowTitle == null,直接跳过该列,生成的 SQL 里根本不包含is_show_title。
- Jackson 默认
最终 DB 列无任何修改,但整条链路没有一个 4xx/5xx,只有一个安静的 UPDATE 语句少了一列。
module-core 里的 entity 同时被多个 Boot 进程打包进各自的 FAT JAR。新增字段后必须重新构建所有消费该 entity 的模块,否则写端进程在 JVM 内存里持有的仍是旧版 class,Jackson 静默丢字段,MyBatis-Plus 静默跳过 null 列,最终 DB 无变化但返回 200。
快速确认方法:ssh server "ls -l /data/jar/app/web/web-*.jar" 检查 mtime,若还是上次发版时间,写端 JAR 没有更新。
正解
改动任何共享实体后,必须重新构建并部署全部消费模块:
# 1. 重新构建所有消费该 entity 的模块(-am 会自动构建依赖的 module-core)
mvn clean package -pl mp -am -DskipTests
mvn clean package -pl web -am -DskipTests
# 2. 验证新 JAR 里确实含有新方法
javap -cp web/target/web-*.jar:module-core/target/*.jar \
com.example.core.entity.Activity \
| grep -i isShowTitle
# 期望输出: getIsShowTitle / setIsShowTitle
# 3. 备份旧 JAR(无 CI/CD 时必做,出问题能快速回滚)
ssh root@SERVER "cp /data/jar/app/web/web-*.jar /data/jar/app/web/web-*.jar.bak-$(date +%Y%m%d%H%M)"
# 4. 两个模块都上传并重启
scp mp/target/mp-*.jar root@SERVER:/data/jar/app/mp/
scp web/target/web-*.jar root@SERVER:/data/jar/app/web/
ssh root@SERVER "cd /data/jar/app/mp && bash star.sh stop && bash star.sh start"
ssh root@SERVER "cd /data/jar/app/web && bash star.sh stop && bash star.sh start"
# 5. 确认两个端口都健康
ssh root@SERVER "curl -s localhost:8050/actuator/health && curl -s localhost:8250/actuator/health"部署完成后用 SQL 验证写端真正落库:
SELECT id, is_show_title, update_at
FROM activity
WHERE id = '<刚才保存的 id>';
-- 期望: is_show_title IS NOT NULL用 -pl <module> -am 逐一重新打包所有 Boot 进程,用 javap | grep 验证新方法存在后再 scp。没有 CI/CD 的项目尤其容易只更新「当前在改的那个模块」,写端漏掉才是高发场景。
一句话外卖
多 Boot 进程共享实体的项目,entity 改一个字段 = 每个进程的 FAT JAR 都要重打包重部署,Jackson 和 MyBatis-Plus 的静默策略会让你完全察觉不到遗漏了哪个进程。