You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何@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的集合中添加嵌入对象时,手动注入审计字段值:

  1. 修改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;
}
  1. 在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并设置审计字段:

  1. 创建自定义监听器:
@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;
    }
}
  1. 注册监听器到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 13:32:57