Spring Data Envers支持有條件變動紀(jì)錄的保存和查詢的方法
數(shù)據(jù)審計是業(yè)務(wù)系統(tǒng)的一個基本能力,需要系統(tǒng)能夠?qū)㈥P(guān)鍵數(shù)據(jù)的變動紀(jì)錄都保存下來,并支持變動紀(jì)錄的查詢。
通過spring-data-envers可以很容易的實現(xiàn)數(shù)據(jù)變動紀(jì)錄的保存和查詢。
有些情況下,我們需要只保存滿足特定條件的數(shù)據(jù)變動紀(jì)錄,不滿足條件的變動紀(jì)錄不進(jìn)行保存,例如只保存某個字段有值的變動紀(jì)錄。
本文介紹支持有條件變動紀(jì)錄的保存和查詢的方法。
具體的代碼參照 示例項目 https://github.com/qihaiyan/springcamp/tree/master/spring-data-envers-conditional
一、概述
可以通過 spring-data-envers 很容易的實現(xiàn)變動紀(jì)錄的保存和查詢,只需要增加幾個注解就可以。但是要實現(xiàn)有條件的變動紀(jì)錄的保存和查詢就需要進(jìn)行一些復(fù)雜的處理。
二、使用 spring-data-envers
首先引入 spring-data-envers 依賴。
在 build.gradle 中增加一行代碼:
implementation 'org.springframework.data:spring-data-envers'
在實體類上增加 Audited 注解:
@Data
@Entity
@Audited
public class MyData {
@Id
@GeneratedValue
private Long id;
private String author;
}Repository 擴(kuò)展 RevisionRepository 方法:
public interface MyDataRepository extends JpaRepository<MyData, Long>, RevisionRepository<MyData, Long, Integer> {
}通過以上3步操作,就添加好了變動紀(jì)錄的保存功能,我們可以通過調(diào)用變動紀(jì)錄查詢方法確認(rèn)變動紀(jì)錄保存成功。
當(dāng) Repository 擴(kuò)展 RevisionRepository 方法后,會有一個默認(rèn)實現(xiàn)的 findRevisions 方法,我們可以直接調(diào)用:
public Revisions<Integer, MyData> findRevisions(Long id) {
return myDataRepository.findRevisions(id);
}最后我們可以執(zhí)行完整的主體數(shù)據(jù)的保存,在控制臺中打印變動紀(jì)錄:
@Override
public void run(String... args) {
MyData myData = new MyData();
myData.setId(1L);
myData.setAuthor("test");
dbService.saveData(myData);
dbService.findRevisions(myData.getId()).forEach(r -> System.out.println("revision: " + r.toString()));
myData.setAuthor("newAuthor");
dbService.saveData(myData);
dbService.findRevisions(myData.getId()).forEach(r -> System.out.println("revision: " + r.toString()));
}執(zhí)行完程序后,可以看到兩次保存數(shù)據(jù)的操作都可以查詢到對應(yīng)的變動紀(jì)錄,并且變動紀(jì)錄還通過 revisionType 顯示了是插入還是更新操作:
revision: Revision 1 of entity MyData(id=1, author=test) - Revision metadata DefaultRevisionMetadata{entity=DefaultRevisionEntity(id = 1, revisionDate = Oct 15, 2023, 11:41:15 AM), revisionType=INSERT}
revision: Revision 2 of entity MyData(id=1, author=newAuthor) - Revision metadata DefaultRevisionMetadata{entity=DefaultRevisionEntity(id = 2, revisionDate = Oct 15, 2023, 11:41:16 AM), revisionType=UPDATE}
三、通過自定義 Event Listener 實現(xiàn)有條件的變動紀(jì)錄的保存
在進(jìn)行數(shù)據(jù)變動時, Envers 通過監(jiān)聽事件來進(jìn)行對應(yīng)的處理,總共有以下幾個監(jiān)聽事件:
EventType.POST_INSERT EventType.PRE_UPDATE EventType.POST_UPDATE EventType.POST_DELETE EventType.POST_COLLECTION_RECREATE EventType.PRE_COLLECTION_REMOVE EventType.PRE_COLLECTION_UPDATE
每個監(jiān)聽事件都對應(yīng)著特定的 Listener ,在本文實例中,我們期望當(dāng) author 的值被更新為空時,不保存變動紀(jì)錄,我們可以通過自定義 PRE_UPDATE 和 POST_UPDATE 的Listener來實現(xiàn)。
因為框架提供了默認(rèn)的Listener,因此自定義 Listener 只需要擴(kuò)展默認(rèn)的Listener,并加入我們自己的特有邏輯就可以。
MyEnversPostUpdateEventListenerImpl :
public class MyEnversPreUpdateEventListenerImpl extends EnversPreUpdateEventListenerImpl {
public MyEnversPreUpdateEventListenerImpl(EnversService enversService) {
super(enversService);
}
@Override
public boolean onPreUpdate(PreUpdateEvent event) {
if (event.getEntity() instanceof MyData
&& ((MyData) event.getEntity()).getAuthor() == null) {
return false;
}
return super.onPreUpdate(event);
}
}MyEnversPostUpdateEventListenerImpl:
public class MyEnversPostUpdateEventListenerImpl extends EnversPostUpdateEventListenerImpl {
public MyEnversPostUpdateEventListenerImpl(EnversService enversService) {
super(enversService);
}
@Override
public void onPostUpdate(PostUpdateEvent event) {
if (event.getEntity() instanceof MyData && ((MyData) event.getEntity()).getAuthor() == null) {
return;
}
super.onPostUpdate(event);
}
}在自定義 Listener 中,我們增加了 對于 author 字段是否為空的判斷邏輯。
四、自定義 Event Listener 注冊到系統(tǒng)中
自定義 Event Listener 完成后,我們還需要讓框架執(zhí)行我們自定義的 Listener, 而不是用默認(rèn)的 Listener。
框架通過 EnversIntegrator 類注冊的 Listener, 我們要做的是重新實現(xiàn) EnversIntegrator , 在本實例中重新實現(xiàn)的類為 MyEnversIntegrator :
public class MyEnversIntegrator implements Integrator {
@Override
public void integrate(Metadata metadata,
BootstrapContext bootstrapContext,
SessionFactoryImplementor sessionFactory) {
final ServiceRegistry serviceRegistry = sessionFactory.getServiceRegistry();
final EnversService enversService = serviceRegistry.getService(EnversService.class);
final EventListenerRegistry listenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
listenerRegistry.addDuplicationStrategy(EnversListenerDuplicationStrategy.INSTANCE);
if (enversService.getEntitiesConfigurations().hasAuditedEntities()) {
listenerRegistry.appendListeners(
EventType.POST_DELETE,
new EnversPostDeleteEventListenerImpl(enversService)
);
listenerRegistry.appendListeners(
EventType.POST_INSERT,
new EnversPostInsertEventListenerImpl(enversService)
);
listenerRegistry.appendListeners(
EventType.PRE_UPDATE,
new MyEnversPreUpdateEventListenerImpl(enversService)
);
listenerRegistry.appendListeners(
EventType.POST_UPDATE,
new MyEnversPostUpdateEventListenerImpl(enversService)
);
listenerRegistry.appendListeners(
EventType.POST_COLLECTION_RECREATE,
new EnversPostCollectionRecreateEventListenerImpl(enversService)
);
listenerRegistry.appendListeners(
EventType.PRE_COLLECTION_REMOVE,
new EnversPreCollectionRemoveEventListenerImpl(enversService)
);
listenerRegistry.appendListeners(
EventType.PRE_COLLECTION_UPDATE,
new EnversPreCollectionUpdateEventListenerImpl(enversService)
);
}
}
@Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
// nothing to do
}
}通過代碼可以發(fā)現(xiàn),我們只是修改了 PRE_UPDATE 和 POST_UPDATE 注冊的 Listener , 其它事件的 Listener 仍然用框架默認(rèn)的。
最后我們需要把我們實現(xiàn)的 MyEnversIntegrator 放到 META-INF/services/org.hibernate.integrator.spi.Integrator 這個配置文件中。
cn.springcamp.springdata.envers.MyEnversIntegrator
五、確認(rèn)有條件變動紀(jì)錄的保存是否生效
最后我們修改控制臺打印程序,將 author 字段更新為 null 并保存,查看變動紀(jì)錄里是否有這個更新操作的紀(jì)錄。
增加保存代碼 :
// won't generate audit record when author is null
myData.setAuthor(null);
dbService.saveData(myData);
dbService.findRevisions(myData.getId()).forEach(r -> System.out.println("revision: " + r.toString()));執(zhí)行程序并觀察控制臺打印內(nèi)容:
revision: Revision 1 of entity MyData(id=1, author=test) - Revision metadata DefaultRevisionMetadata{entity=DefaultRevisionEntity(id = 1, revisionDate = Oct 15, 2023, 11:41:15 AM), revisionType=INSERT}
revision: Revision 2 of entity MyData(id=1, author=newAuthor) - Revision metadata DefaultRevisionMetadata{entity=DefaultRevisionEntity(id = 2, revisionDate = Oct 15, 2023, 11:41:16 AM), revisionType=UPDATE}通過打印內(nèi)容可以確認(rèn),author 字段更新為 null 的變動紀(jì)錄沒有被紀(jì)錄,說明我們的處理是生效的。
到此這篇關(guān)于Spring Data Envers支持有條件變動紀(jì)錄的保存和查詢的方法的文章就介紹到這了,更多相關(guān)Spring Data Envers內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot配置管理Externalized?Configuration深入探究
這篇文章主要介紹了Springboot配置管Externalized?Configuration深入探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
Java如何使用Optional與Stream取代if判空邏輯(JDK8以上)
這篇文章主要給大家介紹了關(guān)于Java如何使用Optional與Stream取代if判空邏輯(JDK8以上)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
SpringBoot處理form-data表單接收對象數(shù)組的方法
form-data則是一種更加靈活的編碼方式,它可以處理二進(jìn)制數(shù)據(jù)(如圖片、文件等)以及文本數(shù)據(jù),這篇文章主要介紹了SpringBoot處理form-data表單接收對象數(shù)組,需要的朋友可以參考下2023-11-11
@scope("prototype") @loadbalanced注解負(fù)載均衡失效問題
這篇文章主要為大家介紹了@scope("prototype") @loadbalanced注解負(fù)載均衡失效問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
解決執(zhí)行Junit單元測試報錯java.lang.ClassNotFoundException問題
這篇文章主要介紹了解決執(zhí)行Junit單元測試報錯java.lang.ClassNotFoundException問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11

