Docker部署Spring/Hibernate应用ddl-auto=update时不自动删列问题
现象本质说明
这个是完全符合预期的行为,不存在Docker构建或者Spring Boot加载的bug,是Hibernate DDL策略+Docker容器数据存储规则共同作用的结果:
- 首先看你配置的
SPRING_JPA_HIBERNATE_DDL_AUTO=update,这个策略的设计逻辑天生就不会执行删除列、删除表这类破坏性操作:当你给实体类新增字段时,Hibernate能检测到表结构缺少对应列,自动执行ALTER语句加列;但你移除实体字段时,Hibernate不会主动删除数据库中已存在的对应列——这是刻意做的安全设计,避免自动DDL误删存量业务数据。这个行为和你重建多少次app容器都没有关系。 - 再解释为什么执行
docker system prune之后冗余列消失了:你当前的PostgreSQL服务没有配置持久化卷,所有数据库数据都存在db容器的内部可写层中。你之前执行docker-compose up --build时,只会重新构建、重启改动过的app服务,已经存在的db容器不会被删除,里面存的带冗余列的旧库数据会被直接复用,Hibernate的update策略又不会删列,自然冗余列会一直保留。 - 当你执行
docker system prune时,所有停止状态的未关联容器都会被强制删除,存储旧数据的db容器也在清理范围内,下次执行up命令时会创建一个全新的空PostgreSQL容器,Hibernate基于你最新的实体类从零初始化表结构,当然就看不到之前的冗余列了。 - 至于你修改Controller代码后重建就能生效,是因为Controller逻辑直接打包在app服务的镜像内,--build参数会重新构建app镜像、替换旧的app容器,新容器里的代码自然是最新版本,这个流程和db容器的存量数据完全不相关,不存在逻辑矛盾。
可调整的配置方案
根据你的使用场景,可以选择对应的调整方式:
- 如果是纯本地开发环境,希望所有实体类变更(包括删字段、修改字段类型)都能自动同步到数据库,可以把环境变量里的ddl-auto值改成
create-drop:该配置会在应用启动时先删除所有存量表,再根据当前实体类重新建表,应用关闭时也会清空表结构。注意:该配置会清空所有数据库数据,绝对不能在生产环境使用。 - 如果本地开发时不想因为改个实体就清空全库测试数据,不要依赖Hibernate自动执行删列这类高风险操作,手动执行对应ALTER语句删列即可;生产环境更推荐使用Flyway、Liquibase这类数据库版本迁移工具管理schema变更,所有结构变更都通过可追溯的SQL脚本执行,稳定性和可控性远高于Hibernate自动DDL。
- 如果需要数据库数据在容器重建、删除后依然保留,可以给db服务添加Docker命名卷做持久化,配置参考如下:
version: '3' services: db: image: 'postgres:13' container_name: db environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password - POSTGRES_DB=restaurant ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data app: build: . container_name: app ports: - "8080:8080" depends_on: - db environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/restaurant - SPRING_DATASOURCE_USERNAME=postgres - SPRING_DATASOURCE_PASSWORD=password - SPRING_JPA_HIBERNATE_DDL_AUTO=update volumes: postgres_data:
添加该配置后,即使db容器被删除、执行了system prune,存在Docker卷中的数据库数据也不会丢失。
生产环境禁止使用
update/create/create-drop这类Hibernate自动DDL配置,自动生成的表结构往往缺少必要的索引、字段约束也可能不符合业务预期,容易引发性能问题甚至数据错误。
内容的提问来源于stack exchange,提问作者Denys Kisliak
相关产品推荐
相关产品推荐

