返回博客

Spring Boot 多模块只更新一个 JAR,写操作无声失败

手动部署 Spring Boot 多模块项目时踩过最隐蔽的坑之一:在共享实体上加了一个字段,前端提交后响应码 200、页面正常关闭,但打开数据库一看——列还是 NULL。这个坑没有任何报错,整整排查了一个小时。

现象

某后台管理系统采用典型的多模块拆分:module-core(共享实体)、mp(端口 8050,C 端只读 API)、web(端口 8250,管理端写入 API)。在 Activity.java 上新增字段 isShowTitle,手动 mvn package 后只 scpmp 模块的 JAR 并重启。

C 端读取正常,但管理端保存后该字段始终为 NULL,响应却是 HTTP 200,没有任何异常日志。

根因

两个 Spring Boot 进程各自打包了一份 Activity.class。只重新部署 mp 后:

  1. mp 进程持有新 class,C 端 GET 能返回字段。
  2. web 进程持有旧 class(JAR mtime 仍是上次部署时间),反序列化时触发两个静默行为:
    • Jackson 默认 FAIL_ON_UNKNOWN_PROPERTIES=false@RequestBody Activity activity 把请求体里的 isShowTitle 字段视为"未知属性",静默丢弃,不抛异常。
    • MyBatis-Plus 默认 FieldStrategy.NOT_NULLupdateById(activity) 发现 entity.isShowTitle == null,直接跳过该列,生成的 SQL 里根本不包含 is_show_title

最终 DB 列无任何修改,但整条链路没有一个 4xx/5xx,只有一个安静的 UPDATE 语句少了一列。

坑:多模块共享实体改动,只部署了读端 JAR

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
正解:改共享 entity = 重部署所有消费模块

-pl <module> -am 逐一重新打包所有 Boot 进程,用 javap | grep 验证新方法存在后再 scp。没有 CI/CD 的项目尤其容易只更新「当前在改的那个模块」,写端漏掉才是高发场景。

一句话外卖

多 Boot 进程共享实体的项目,entity 改一个字段 = 每个进程的 FAT JAR 都要重打包重部署,Jackson 和 MyBatis-Plus 的静默策略会让你完全察觉不到遗漏了哪个进程。