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

如何在Spring JPA Auditing中存储lastModifiedBy的用户列表

实现Spring JPA Audit存储所有编辑用户的可行方案

针对你需要将lastModifiedBy扩展为存储所有编辑用户列表的需求,结合PostgreSQL数据库,提供以下三种可行方案:

方案一:基于PostgreSQL数组类型直接存储

利用PostgreSQL原生的数组类型,在主实体中维护编辑用户列表,每次更新时追加当前用户。

1. 修改抽象审计类

替换原lastModifiedBy字段为用户列表,指定PostgreSQL数组类型的列定义:

@Data
@MappedSuperclass
@EntityListeners({AuditingEntityListener.class, ModifiedByEntityListener.class})
public abstract class Auditable<T> {

    @CreatedBy
    @Column(updatable = false)
    protected T createdBy;

    @Temporal(TemporalType.TIMESTAMP)
    @CreationTimestamp
    @Column(updatable = false)
    protected Date createdDate;

    // 存储所有编辑用户的数组,这里以String类型用户ID为例,若为Long则用bigint[]
    @Column(columnDefinition = "text[]")
    protected List<T> modifiedByUsers;

    @Temporal(TemporalType.TIMESTAMP)
    @UpdateTimestamp
    protected Date lastModifiedDate;
}

2. 自定义实体监听器实现追加逻辑

编写监听器在实体创建和更新时,将当前用户加入列表(可选择去重):

@Component
public class ModifiedByEntityListener {

    @Autowired
    private AuditorAware<String> auditorAware;

    @PrePersist
    public void prePersist(Auditable<String> entity) {
        String currentUser = auditorAware.getCurrentAuditor().orElse(null);
        if (currentUser != null) {
            List<String> users = new ArrayList<>();
            users.add(currentUser);
            entity.setModifiedByUsers(users);
        }
    }

    @PreUpdate
    public void preUpdate(Auditable<String> entity) {
        String currentUser = auditorAware.getCurrentAuditor().orElse(null);
        if (currentUser != null) {
            List<String> users = Optional.ofNullable(entity.getModifiedByUsers()).orElse(new ArrayList<>());
            // 可选:避免同一用户多次编辑重复存储
            if (!users.contains(currentUser)) {
                users.add(currentUser);
            }
            entity.setModifiedByUsers(users);
        }
    }
}

3. 确保AuditorAware配置正常

你需要已实现AuditorAware来获取当前登录用户,例如基于Spring Security的实现:

@Component
public class SecurityAuditorAware implements AuditorAware<String> {
    @Override
    public Optional<String> getCurrentAuditor() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null || !auth.isAuthenticated()) {
            return Optional.empty();
        }
        return Optional.of(auth.getName());
    }
}

方案二:独立审计日志表(推荐用于复杂审计场景)

创建单独的审计日志表,记录每个实体的所有修改操作及用户信息,避免主表字段冗余。

1. 定义审计日志实体

@Entity
@Table(name = "entity_audit_log")
@Data
public class EntityAuditLog {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "entity_type")
    private String entityType;

    @Column(name = "entity_id")
    private String entityId;

    @Column(name = "modified_by")
    private String modifiedBy;

    @Temporal(TemporalType.TIMESTAMP)
    private Date modifiedDate;

    @Column(name = "operation_type")
    private String operationType; // CREATE/UPDATE/DELETE
}

2. 自定义监听器插入审计日志

@Component
public class EntityAuditListener {

    @Autowired
    private EntityManager entityManager;

    @Autowired
    private AuditorAware<String> auditorAware;

    @PrePersist
    public void prePersist(Object entity) {
        saveAuditLog(entity, "CREATE");
    }

    @PreUpdate
    public void preUpdate(Object entity) {
        saveAuditLog(entity, "UPDATE");
    }

    @PreRemove
    public void preRemove(Object entity) {
        saveAuditLog(entity, "DELETE");
    }

    private void saveAuditLog(Object entity, String operationType) {
        String currentUser = auditorAware.getCurrentAuditor().orElse("anonymous");
        EntityAuditLog log = new EntityAuditLog();
        log.setEntityType(entity.getClass().getSimpleName());
        // 获取实体ID,假设实体有getId()方法
        try {
            Method getIdMethod = entity.getClass().getMethod("getId");
            log.setEntityId(getIdMethod.invoke(entity).toString());
        } catch (Exception e) {
            log.setEntityId("unknown");
        }
        log.setModifiedBy(currentUser);
        log.setModifiedDate(new Date());
        log.setOperationType(operationType);
        entityManager.persist(log);
    }
}

3. 更新抽象审计类

将监听器添加到Auditable类,可移除原lastModifiedBy字段:

@Data
@MappedSuperclass
@EntityListeners({AuditingEntityListener.class, EntityAuditListener.class})
public abstract class Auditable<T> {

    @CreatedBy
    @Column(updatable = false)
    protected T createdBy;

    @Temporal(TemporalType.TIMESTAMP)
    @CreationTimestamp
    @Column(updatable = false)
    protected Date createdDate;

    @Temporal(TemporalType.TIMESTAMP)
    @UpdateTimestamp
    protected Date lastModifiedDate;
}

方案三:使用Hibernate Envers实现完整审计

Hibernate Envers是官方审计框架,自动记录实体所有修改版本及相关审计信息。

1. 添加依赖

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-envers</artifactId>
</dependency>

2. 配置自定义修订实体和监听器

首先定义包含修改用户的修订实体:

@Entity
@RevisionEntity(CustomRevisionListener.class)
@Data
public class CustomRevisionEntity extends DefaultRevisionEntity {
    private String modifiedBy;
}

然后实现修订监听器填充用户信息:

public class CustomRevisionListener implements RevisionListener {
    @Override
    public void newRevision(Object revisionEntity) {
        CustomRevisionEntity revision = (CustomRevisionEntity) revisionEntity;
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.isAuthenticated()) {
            revision.setModifiedBy(auth.getName());
        } else {
            revision.setModifiedBy("anonymous");
        }
    }
}

3. 启用实体审计

在Auditable类添加@Audited注解,让所有继承的实体自动开启审计:

@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Audited
public abstract class Auditable<T> {
    // 原有字段保持不变
}

4. 查询审计历史

通过AuditReader获取实体的所有修订版本及对应的修改用户:

AuditReader auditReader = AuditReaderFactory.get(entityManager);
List<Number> revisions = auditReader.getRevisions(YourEntity.class, entityId);
for (Number rev : revisions) {
    CustomRevisionEntity revision = auditReader.findRevision(CustomRevisionEntity.class, rev);
    String modifiedBy = revision.getModifiedBy();
    Date modifiedDate = revision.getRevisionDate();
    // 获取对应版本的实体数据
    YourEntity entityVersion = auditReader.find(YourEntity.class, entityId, rev);
}

内容的提问来源于stack exchange,提问作者Joe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 10:44:53