如果您需要對所有數(shù)據(jù)庫操作進(jìn)行自動審計,并且您正在使用 Hibernate……您應(yīng)該使用?Envers
?或?spring data jpa auditing
?。但是如果由于某些原因您不能使用 ?Envers
?,您可以使用 ?hibernate
?事件偵聽器和 ?spring
?事務(wù)同步來實現(xiàn)類似的功能。
首先,從事件監(jiān)聽器開始。您應(yīng)該捕獲所有插入、更新和刪除操作。但是有一個棘手的問題——如果您出于任何原因需要刷新會話,則不能使用傳遞給事件偵聽器的會話直接執(zhí)行該邏輯。在我的情況下,我必須獲取一些數(shù)據(jù),并且 ?hibernate
?開始向我拋出異常(“id 為 null”)。多個來源確認(rèn)您不應(yīng)在事件偵聽器中與數(shù)據(jù)庫交互。因此,您應(yīng)該存儲事件以供以后處理。您可以將偵聽器注冊為 spring bean,如下所示:
@Component
public class AuditLogEventListener
implements PostUpdateEventListener, PostInsertEventListener, PostDeleteEventListener {
@Override
public void onPostDelete(PostDeleteEvent event) {
AuditedEntity audited = event.getEntity().getClass().getAnnotation(AuditedEntity.class);
if (audited !=null) {
AuditLogServiceData.getHibernateEvents().add(event);
}
}
@Override
public void onPostInsert(PostInsertEvent event) {
AuditedEntity audited = event.getEntity().getClass().getAnnotation(AuditedEntity.class);
if (audited !=null) {
AuditLogServiceData.getHibernateEvents().add(event);
}
}
@Override
public void onPostUpdate(PostUpdateEvent event) {
AuditedEntity audited = event.getEntity().getClass().getAnnotation(AuditedEntity.class);
if (audited !=null) {
AuditLogServiceData.getHibernateEvents().add(event);
}
}
@Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
return true;// Envers sets this to true only if the entity is versioned. So figure out for yourself if that's needed
}
}
請注意?AuditedEntity
?- 它是一個自定義標(biāo)記注釋(?retention=runtime
?, ?target=type
?),您可以將其放在實體之上。
由于我有 spring 可供我?使用的?AuditLogServiceData
,所以在我的示例上我決定使用 spring:
/**
* {@link AuditLogServiceStores} stores here audit log information It records all
* changes to the entities in spring transaction synchronizaton resources, which
* are in turn stored as {@link ThreadLocal} variables for each thread. Each thread
* /transaction is using own copy of this data.
*/
public class AuditLogServiceData {
private static final String HIBERNATE_EVENTS ="hibernateEvents";
@SuppressWarnings("unchecked")
public static List<Object> getHibernateEvents() {
if (!TransactionSynchronizationManager.hasResource(HIBERNATE_EVENTS)) {
TransactionSynchronizationManager.bindResource(HIBERNATE_EVENTS,new ArrayList<>());
}
return (List<Object>) TransactionSynchronizationManager.getResource(HIBERNATE_EVENTS);
}
public static Long getActorId() {
return (Long) TransactionSynchronizationManager.getResource(AUDIT_LOG_ACTOR);
}
public static void setActor(Long value) {
if (value !=null) {
TransactionSynchronizationManager.bindResource(AUDIT_LOG_ACTOR, value);
}
}
public void clear() {
// unbind all resources
}
}
除了存儲事件之外,我們還需要存儲正在執(zhí)行操作的用戶。為了得到它,我們需要提供一個方法參數(shù)級別的注釋來指定一個參數(shù)。在我的例子中的注釋被稱為?AuditLogActor(retention=runtime, type=parameter)
?。
現(xiàn)在剩下的是處理事件的代碼。我們希望在提交當(dāng)前事務(wù)之前執(zhí)行此操作。如果事務(wù)在提交時失敗,審計條目插入也將失敗。我們用一點 AOP 來做到這一點:
@Aspect
@Component
class AuditLogStoringAspectextends TransactionSynchronizationAdapter {
@Autowired
private ApplicationContext ctx;
@Before("execution(* *.*(..)) && @annotation(transactional)")
public void registerTransactionSyncrhonization(JoinPoint jp, Transactional transactional) {
Logger.log(this).debug("Registering audit log tx callback");
TransactionSynchronizationManager.registerSynchronization(this);
MethodSignature signature = (MethodSignature) jp.getSignature();
int paramIdx =0;
for (Parameter param : signature.getMethod().getParameters()) {
if (param.isAnnotationPresent(AuditLogActor.class)) {
AuditLogServiceData.setActor((Long) jp.getArgs()[paramIdx]);
}
paramIdx ++;
}
}
@Override
public void beforeCommit(boolean readOnly) {
Logger.log(this).debug("tx callback invoked. Readonly= " + readOnly);
if (readOnly) {
return;
}
for (Object event : AuditLogServiceData.getHibernateEvents()) {
// handle events, possibly using instanceof
}
}
@Override
public void afterCompletion(int status) {
// we have to unbind all resources as spring does not do that automatically
AuditLogServiceData.clear();
}
就我而言,我不得不注入額外的服務(wù),而 spring 抱怨相互依賴的 bean,所以我改為使用?applicationContext.getBean(FooBean.class)
?. 注意:確保您的方面被 spring 捕獲 - 通過自動掃描或通過 xml/java-config 顯式注冊它。
因此,經(jīng)過審計的調(diào)用將如下所示:
@Transactional
public void saveFoo(FooRequest request,@AuditLogActor Long actorId) { .. }
總結(jié)一下:hibernate 事件監(jiān)聽器將所有插入、更新和刪除事件存儲為 Spring 事務(wù)同步資源。一個方面向 spring 注冊一個事務(wù)“回調(diào)”,它在每個事務(wù)提交之前被調(diào)用。在那里處理所有事件并插入相應(yīng)的審計日志條目。
這是非?;镜膶徲嬋罩?,它可能在收集處理方面存在問題,而且它肯定沒有涵蓋所有用例。但它比手動審計日志處理要好得多,并且在許多系統(tǒng)中,審計日志是強(qiáng)制性功能。