Spring Boot新增非空默认列后如何正确保存实体?
场景代码
SQL语句
ALTER TABLE users ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
Hibernate实体类
@Entity @Table(name = "users") public class User { ... @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; ... }
用户问题
保存新User实体时,是否需将createdAt字段留空?Hibernate会自动使用数据库指定的默认值吗?正确的操作方式是什么?我查阅了Hibernate手册但未找到相关案例,不清楚其底层实现逻辑。
解答
直接空着或者设为null行不通:要是你不给
createdAt赋值或者设成null,Hibernate生成插入SQL时会明确把这个字段写为null,但数据库里这个字段定义了NOT NULL,直接就会抛非空约束异常,根本不会触发你设的now()默认值。Hibernate不会主动用数据库默认值:除非你专门配置,不然Hibernate压根不知道数据库层面的默认值规则,它只会老老实实根据实体字段的当前值生成SQL语句。
三种靠谱的操作方式
实体层直接初始化+指定字段定义
给字段直接初始化当前时间,同时在@Column里明确数据库字段定义,结合updatable = false确保后续不会被修改:@Column(name = "created_at", nullable = false, updatable = false, columnDefinition = "TIMESTAMP NOT NULL DEFAULT now()") private LocalDateTime createdAt = LocalDateTime.now();这种方式不管是Hibernate生成SQL还是数据库兜底,都能保证值没问题,还能避免跨数据库的兼容性坑。
用Hibernate的
@CreationTimestamp注解
这是Hibernate专门给创建时间字段准备的注解,实体插入时会自动帮你把当前时间赋值给这个字段,完全不用手动管:@Column(name = "created_at", nullable = false, updatable = false) @CreationTimestamp private LocalDateTime createdAt;好处是不用操心数据库的默认值配置,代码也更干净,Hibernate内部会处理好时间赋值的逻辑。
让Hibernate忽略插入该字段,触发数据库默认值
如果你铁了心要用数据库的now(),可以把字段的insertable设为false,这样Hibernate生成插入SQL时就不会包含这个字段,数据库就会自动用默认值:@Column(name = "created_at", nullable = false, updatable = false, insertable = false) private LocalDateTime createdAt;但要注意:插入完之后如果想立刻拿到这个
createdAt的值,Hibernate不会自动去数据库刷新,得手动调用entityManager.refresh(user)才能获取到正确的时间。
底层逻辑拆解
Hibernate生成SQL的逻辑很直接:
- 要是字段没赋值且
insertable = true,它就会在SQL里把这个字段设为null; - 只有当
insertable = false时,Hibernate才会在插入SQL里去掉这个字段,这时数据库才会启用自己的默认值规则; @CreationTimestamp是Hibernate的扩展功能,在实体准备插入的生命周期阶段,它会自动给字段赋值当前时间,本质是Java层面的处理,和数据库默认值没关系。
内容的提问来源于stack exchange,提问作者dev-007

