Spring JPA:通过@Query执行部分更新时@LastModifiedDate未更新的问题
没错,你遇到的情况确实是因为这种自定义的部分更新操作不会触发@LastModifiedDate的自动更新,我来给你拆解原因和解决办法:
核心原因
@LastModifiedDate是Spring Data JPA审计功能的一部分,它的工作原理是**通过实体的生命周期回调(比如@PreUpdate注解触发的方法)**来自动填充时间戳。而你写的自定义@Modifying查询是直接执行JPQL/SQL语句操作数据库,完全绕过了JPA的持久化上下文(Persistence Context),不会触发实体的任何生命周期事件,自然也就不会触发@LastModifiedDate的自动更新逻辑。
简单来说:这种批量/部分更新是“直接操作数据库”,而不是“修改JPA管理的实体对象后同步到数据库”,所以审计注解不会生效。
解决办法
有两种常见的方案可以解决这个问题:
方案1:通过实体对象修改(推荐,符合JPA规范)
先查询出对应的Item实体,修改属性后让JPA自动处理更新,这样会触发生命周期回调,@LastModifiedDate会被自动填充:
@Transactional void updatePrice(Long id, Double price, Long version) { // 先查询实体,让它进入持久化上下文 Item item = itemRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("指定ID的商品不存在")); // 乐观锁校验 if (item.getVersion() < version) { item.setPrice(price); item.setVersion(version); // 不需要手动调用save(),事务提交时JPA会自动检测到实体变更并同步到数据库 } }
方案2:在自定义@Query中手动更新时间字段
如果一定要保留自定义@Modifying查询的方式,那需要在JPQL语句中手动指定lastModifiedDate的更新逻辑,比如用JPQL的跨数据库时间函数CURRENT_TIMESTAMP:
@Transactional @Modifying @Query("UPDATE Item s SET s.price= :price, s.version= :version, s.lastModifiedDate = CURRENT_TIMESTAMP WHERE s.id= :id and s.version < :version") void updatePrice(@Param("id") Long id, @Param("price") Double price, @Param("version") Long version);
注意:如果你的数据库有特定的时间函数(比如MySQL的NOW()、PostgreSQL的CURRENT_TIMESTAMP),也可以替换使用,但CURRENT_TIMESTAMP是JPQL标准函数,兼容性更好。
额外提醒
所有Spring Data JPA的审计注解(@CreatedDate、@LastModifiedDate、@CreatedBy、@LastModifiedBy)都依赖于实体的生命周期事件,只有当操作通过JPA持久化上下文管理的实体时才会生效。比如调用save()、saveAll(),或者查询实体后修改属性,这些操作都会触发对应的回调,自动填充审计字段。
内容的提问来源于stack exchange,提问作者Prabhat Ranjan

