为何@CreatedDate注解在@ElementCollection中无法生效?
@ElementCollection中@CreatedDate注解失效问题解决
问题描述
使用Spring Data JPA审计功能时,@CreatedDate和@CreatedBy注解在@ElementCollection关联的嵌入对象(@Embeddable)中完全无法生效。改用@OneToMany关联实体时审计功能正常,但业务需求必须采用值对象(Value Object)的实现形式。
环境版本
- Java版本:JDK 19
- Spring Boot版本:3.0.2
- JPA版本:3.0.2
- Hibernate.core版本:6.1.6.Final
相关代码
User.java
@Table(name = "sys_user") @DynamicUpdate @Getter @RequiredArgsConstructor @NoArgsConstructor @ToString(callSuper = true) @Entity @EntityListeners(AuditingEntityListener.class) public final class User { @NonNull @NotNull @EmbeddedId private UserId id; @Embedded private AuditingTime auditingTime = new AuditingTime(); @Embedded private AuditingUser auditingUser = new AuditingUser(); @ElementCollection private final Set<Password> passwords = new HashSet<>(); @ElementCollection @CollectionTable private final Set<RoleItem> roleItems = new HashSet<>(); @Getter(AccessLevel.PRIVATE) @JsonIgnore @Transient private transient final List<UserEvent> domainEvent = new ArrayList<>(); public static User of(CreateUserCommand command) { User user = new User( UserIdGenerator.newId(), command.getUsername(), command.getEmail(), command.getExpiredTime()); ValidationUtils.validate(user); return user; } }
RoleItem.java
@Getter @EqualsAndHashCode @RequiredArgsConstructor @AllArgsConstructor @NoArgsConstructor @Embeddable public class RoleItem { @NonNull @NotNull private Long roleId; @CreatedBy private Long createdBy; @CreatedDate private LocalDateTime createdAt; public static RoleItem of(CreateRoleItemCommand command) { RoleItem roleItem = new RoleItem(command.getRoleId()); ValidationUtils.validate(roleItem); return roleItem; } }
JpaConfig.java
@Configuration @EnableJpaAuditing public class JpaConfig { @Bean public AuditorAware<Long> auditorAware() { return () -> Optional.of(1L); } }
已尝试的无效操作
- 将所有构造器权限改为
public,无效果 - 移除集合属性的
final修饰符,问题仍存在 - 改用
@OneToMany关联实体时审计功能正常,但不符合值对象的业务需求
问题原因
Spring Data JPA默认的AuditingEntityListener仅作用于标注@Entity的实体类,不会递归处理@ElementCollection中的@Embeddable嵌入对象,因此嵌入对象内的@CreatedDate/@CreatedBy注解不会被触发执行。
解决方案
方案1:手动设置审计字段(简单直接)
在向User的集合中添加嵌入对象时,手动注入审计字段值:
- 修改
RoleItem的of方法,支持传入审计参数:
public static RoleItem of(CreateRoleItemCommand command, Long createdBy, LocalDateTime createdAt) { RoleItem roleItem = new RoleItem(command.getRoleId(), createdBy, createdAt); ValidationUtils.validate(roleItem); return roleItem; }
- 在
User类中添加添加角色项的方法,从AuditorAware获取当前审计信息并设置:
public void addRoleItem(CreateRoleItemCommand command, AuditorAware<Long> auditorAware) { Long createdBy = auditorAware.getCurrentAuditor().orElseThrow(() -> new IllegalStateException("无法获取当前审计用户")); RoleItem roleItem = RoleItem.of(command, createdBy, LocalDateTime.now()); this.roleItems.add(roleItem); }
方案2:自定义Hibernate监听器(自动化处理)
编写自定义监听器,在实体插入/更新时自动遍历ElementCollection并设置审计字段:
- 创建自定义监听器:
@Component public class EmbeddableAuditingListener implements PreInsertEventListener, PreUpdateEventListener { private final AuditorAware<Long> auditorAware; public EmbeddableAuditingListener(AuditorAware<Long> auditorAware) { this.auditorAware = auditorAware; } @Override public boolean onPreInsert(PreInsertEvent event) { Object entity = event.getEntity(); if (entity instanceof User user) { LocalDateTime now = LocalDateTime.now(); Long createdBy = auditorAware.getCurrentAuditor().orElse(null); // 处理RoleItem集合 user.getRoleItems().forEach(item -> { item.setCreatedBy(createdBy); item.setCreatedAt(now); }); // 同理处理Password集合 user.getPasswords().forEach(password -> { // 假设Password也有审计字段,此处对应设置 }); } return false; // 返回false不阻止Hibernate默认操作 } @Override public boolean onPreUpdate(PreUpdateEvent event) { // 若需要处理更新时的审计字段,可参考PreInsert逻辑实现 return false; } }
- 注册监听器到Hibernate:
@Configuration public class HibernateConfig { @Autowired private EmbeddableAuditingListener auditingListener; @Bean public HibernatePropertiesCustomizer hibernatePropertiesCustomizer() { return properties -> { EventListenerRegistry registry = ((SessionFactoryImplementor) properties.get("hibernate.session_factory")).getServiceRegistry().getService(EventListenerRegistry.class); registry.prependListeners(EventType.PRE_INSERT, auditingListener); registry.prependListeners(EventType.PRE_UPDATE, auditingListener); }; } }
内容的提问来源于stack exchange,提问作者Lyle
相关产品推荐
相关产品推荐

