我们有一个基于休眠的应用程序。
此应用程序已使用拦截器触发创建审计跟踪;它通过挂钩'beforeTransactionComplete'机制来实现这一点,将每个实体的最终状态复制到审计表中。
所有实体的版本号都会在发生更改时递增 - 这样可以防止冲突的更新,因为审计表的ID为ID +版本。
这非常有效。我们现在要做的是通过对一些实体数据进行反规范化来实现一些模式性能增强。我们有这些由hibernate支持的实体:
public class Task {
boolean isCompleted;
// ...
}
public class Allocation {
Resource resource;
Task task;
boolean taskIsCompleted; // <-- this is a new field
}
(可能有很多分配,每个分配都指同一个任务)。
taskIsCompleted字段是DBA的一个建议 - 从任务中复制isCompleted值,这样查询就可以避免加入Task来发现isCompleted = false。
实现此目的的一种方法是在更新任务时使用数据库触发器。但是,这对于我们的用例来说是不可接受的,因为a)触发器将我们绑定到特定的数据库实现,但是b)它在审计触发器的“后面”完成 - 因此该字段将被调整而不需要其他审计功能正确。我们必须更新此实体,“好像”业务逻辑已手动更新它。
所以,我希望这可以通过hibernate中的事件监听器来实现。但我不确定这种方法是多么“安全”,并且存在许多问题。
我的方法是在SaveOrUpdateEventListener和FlushEntityEventListener上注册侦听器,执行如下操作:
public void onFlushEntity(FlushEntityEvent event) throws HibernateException {
if( event.getEntity() instanceof Task) {
// Find all the allocations for this task
Session session = event.getSession();
Query hq = session.createQuery("from Allocation alloc where alloc.task.id = :id");
hq.setParameter("id", task.getId());
Collection<Allocation> allocations = hq.list();
// And update them
for( Allocation allocation : allocations ) {
allocation.setTaskComplete( task.getIsCompleted() );
session.save(allocation);
}
}
}
第一个问题是在这里执行查询会导致hibernate'重新刷新'所有实体,导致最终的堆栈溢出(IE:hq.list()导致flush,调用onFlushEntity,.... )。
我可以通过保留一个“已经刷新”的任务列表来防范这种情况,但在我最初的尝试中,我也看到了审计失败(因为它试图将行复制两次)。
在Hibernate中有没有做过这种事情的例子?我最担心的是我能够一起破解它,但是错过了一些重要的边缘情况,当我最不期待它时,它会在生产中失败..
更新: 似乎执行查询真的会让事情变得混乱。 Hibernate重新调用'onFlushEntity'以便(可能)尝试在查询之前将Task写回数据库 - 但是,SQL Update实际上从未发生..