6

我尝试记录我的 JPA 实体的任何更改。出于这个原因,每个实体都继承自一个抽象实体类,该实体类具有一个 LogEntry 对象列表。

抽象实体类:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@EntityListeners(ChangeListener.class)
public abstract class AbstractEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Version
    private Long version;
    @Temporal(TemporalType.DATE)
    private Date validFrom;
    @Temporal(TemporalType.DATE)
    private Date validTo;
    private String name;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "abstractEntity")
    private List<LogEntry> logEntry = new ArrayList<LogEntry>();
    //getter and setter
}

LogEntry 类:

@Entity
public class LogEntry extends AbstractEntity {

    @ManyToOne
    @JoinColumn
    protected AbstractEntity abstractEntity;
    @ManyToOne
    @JoinColumn
    protected Person person; // creator or updater
    @Column(updatable=false, insertable=false, columnDefinition="TIMESTAMP DEFAULT   CURRENT_TIMESTAMP")
    @Temporal(TemporalType.TIMESTAMP)
    protected Date changeDate;
    protected String comment;
    //getter and setter
}

我的方法是创建一个新的 LogEntry 对象并将其添加到实体的 LogEntry 列表中,然后再更新或保留实体。

我尝试了以下解决方案:

  • 直接在实体类中使用回调注释(@PreUpdate、@PrePersist 等)或在与 AbstractEntity 连接的实体侦听器中分离
  • 在实体监听器中使用 EclipsLink 的 DescriptorEvent 和相应的回调方法。这是最有希望的试验。在 preUpdate 中,我可以向受影响的对象添加一个新的 LogEntry。添加的 LogEntry 甚至被正确地持久化了,但是 preUpdate 将被任何数据库操作调用(选择也会导致调用 preUpdate),所以我不能区分更改的对象和没有更改的对象。描述符事件、相关查询或 unitOfWork 提供的变更集在每种情况下都是空的。当前对象和旧对象的比较(由描述符事件提供)恕我直言太复杂了,不是吗?另一方面,在 preUpdateWithChanges 中,我可以轻松检测到更改的实体,但此时添加日志条目显然为时已晚。日志条目不会被持久化。

几乎每个试验都使我能够更改受影响实体的属性(如名称或 validTo)。但是没有解决方案提供创建新 LogEntry 实例的机会,或者更确切地说是持久化这个 LogEntry 实例。我还尝试通过 jndi 查找获取会话 bean 的实例,以手动保存 LogEntry。jndi 查找有效,但调用会话 bean 的创建或更新方法无效。

我当前的实体侦听器如下所示:

public class ChangeListener extends DescriptorEventAdapter {

    @Override
    public void preUpdate(DescriptorEvent event) {
        AbstractEntity entity = (AbstractEntity) event.getObject();
        if (!(entity instanceof LogEntry)) {
            LogEntry logEntry = new LogEntry();
            logEntry.setPerson(getSessionController().getCurrentUser());
            logEntry.setAbstractEntity(entity);
            entity.getLogEntries().add(logEntry);
        }
    }
}

由于各种原因,Hibernate Envers 是没有选择的。

EclipseLink 版本是 2.3.2。

4

4 回答 4

8

在提交过程中的事件期间持久化新对象可能很困难。

您可以在提交之前获取更改并保留您的日志(从 UnitOfWork 获取更改集)。

见, http://wiki.eclipse.org/EclipseLink/FAQ/JPA#How_to_access_what_changed_in_an_object_or_transaction.3F

否则,可以直接从事件内部插入和对象。

IE

event.getSession().insertObject(logEntry);
于 2013-04-01T14:43:13.613 回答
4

我暂时通过比较 preUpdate 中的当前实体和旧实体来解决这个问题。比较是使用 Apache Commons Lang 的EqualsBuilder完成

public class ChangeAbstractEntityListener extends DescriptorEventAdapter {

    @Override
    public void preUpdate(DescriptorEvent event) {
        AbstractEntity originalEntity = (AbstractEntity) event.getOriginalObject();
        AbstractEntity entity = (AbstractEntity) event.getObject();

        if (!EqualsBuilder.reflectionEquals(originalEntity, entity, true)) {
            if (!(entity instanceof LogEntry)) {
                LogEntry logEntry = new LogEntry();
                logEntry.setPerson(getSessionController().getCurrentUser());
                logEntry.setAbstractEntity(entity);
                entity.getLogEntries().add(logEntry);
            }
        }
    }
    //jndi lookup to get the user object
}
于 2013-05-17T08:20:05.533 回答
0

您可以尝试使用历史记录策略跟踪更改

EclipseLink 为跟踪对数据库所做的所有更改提供了扩展支持。EclipseLink HistoryPolicy 可以在 ClassDescriptor 上配置以存储原始镜像表,该镜像表将存储对象在任何时间点的状态。这可用于审计目的,或允许查询过去的时间点,或允许恢复旧数据。

于 2016-06-03T09:26:15.183 回答
-1

如果您想要简单的实体审计,那么 Hibernate 的 Envers 模块就是您的最佳选择。

它适用于 JPA(和 Spring-Data),并且可以存储每个更改的版本以及更改了它,如果你运行说 Spring Security。

Envers 自 3.6 起成为 Hibernate 的一部分

于 2013-05-22T01:29:24.500 回答