Spring Boot+Hibernate中merge()导致created_date字段为空的问题
SpringBoot+Hibernate中更新现有记录时created_date变为null的问题分析
问题场景
我有一个SpringBoot + Hibernate Web项目,相关代码如下:
Course实体类
@Entity public class Course { @Id @GeneratedValue private Long id; private String name; @UpdateTimestamp private LocalDateTime lastUpdatedDate; @CreationTimestamp private LocalDateTime createdDate; // 省略getter/setter等方法 }
CourseRepository类
@Repository @Transactional public class CourseRepository { @Autowired EntityManager em; public Course save(Course course) { em.merge(course); } // 省略其他方法 }
data.sql初始化数据
insert into course(id, name, created_date, last_updated_date) values (10001, 'abc', CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP()); insert into course(id, name, created_date, last_updated_date) values (10002, 'abc', CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP()); insert into course(id, name, created_date, last_updated_date) values (10003, 'abc', CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP());
DemoApplication启动类
public class DemoApplication implements CommandLineRunner{ @Autowired private CourseRepository repository; public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Override public void run(String... args) throws Exception { repository.save(new Course(10002L, "abcdefghfi")); } }
现象
运行程序后,H2数据库中Course表数据如下:
| ID | CREATED_DATE | LAST_UPDATED_DATE | NAME |
|---|---|---|---|
| 10001 | 2022-11-02 23:13:51.926762 | 2022-11-02 23:13:51.926762 | abc |
| 10002 | null | 2022-11-02 23:13:52.325534 | abcdefghfi |
| 10003 | 2022-11-02 23:13:51.930761 | 2022-11-02 23:13:51.930761 | abc |
补充:当传入不存在的ID(如1L)时,新增的记录created_date字段正常赋值,不会为null。
原因分析
@CreationTimestamp的触发逻辑:该注解仅在实体**首次被持久化(插入操作)**时自动生成时间戳,更新操作不会触发它的赋值逻辑。merge方法的行为:当调用em.merge(course)时,若数据库中已存在对应ID的记录,Hibernate会执行更新操作。你创建的new Course(10002L, "abcdefghfi")对象中createdDate属性为null,merge操作会将这个null值覆盖到数据库的现有记录中,导致原有的created_date被置空。- 新增场景的差异:当ID不存在时,merge会执行插入操作,此时
@CreationTimestamp生效,自动填充时间戳,所以created_date不会为null。
解决方案
方案1:查询后更新指定字段
修改save方法,先查询现有实体,仅更新需要修改的字段,避免覆盖createdDate:
public Course save(Course course) { if (course.getId() != null) { Course existingCourse = em.find(Course.class, course.getId()); if (existingCourse != null) { // 仅更新需要修改的字段,比如name existingCourse.setName(course.getName()); // @UpdateTimestamp会自动更新lastUpdatedDate,无需手动设置 return em.merge(existingCourse); } } // 新增逻辑 em.persist(course); return course; }
方案2:标记createdDate为不可更新
在实体类的createdDate字段上添加@Column(updatable = false),确保该字段不会被更新操作修改:
@CreationTimestamp @Column(updatable = false) private LocalDateTime createdDate;
这样即使merge时传入的实体createdDate为null,也不会覆盖数据库中的原有值。
方案3:启用动态更新
在实体类上添加@DynamicUpdate注解,Hibernate会仅生成包含修改字段的SQL语句,不会触及未修改的createdDate:
@Entity @DynamicUpdate public class Course { @Id @GeneratedValue private Long id; private String name; @UpdateTimestamp private LocalDateTime lastUpdatedDate; @CreationTimestamp private LocalDateTime createdDate; // 省略其他方法 }
内容的提问来源于stack exchange,提问作者Ryannnnnnnnn
相关产品推荐
相关产品推荐

