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)文章
java注解結(jié)合aspectj AOP進(jìn)行日志打印的操作
這篇文章主要介紹了java注解結(jié)合aspectj AOP進(jìn)行日志打印的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02java中統(tǒng)一返回前端格式及統(tǒng)一結(jié)果處理返回詳解
這篇文章主要介紹了統(tǒng)一結(jié)果處理的重要性,以及如何在SpringBoot項目中定義和使用統(tǒng)一結(jié)果返回類,通過構(gòu)造器私有化和靜態(tài)方法ok、error,提供了成功和失敗的統(tǒng)一響應(yīng)格式,需要的朋友可以參考下2025-02-02SpringBoot+JavaMailSender實現(xiàn)騰訊企業(yè)郵箱配置
這篇文章主要介紹了SpringBoot+JavaMailSender實現(xiàn)騰訊企業(yè)郵箱配置,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04GC算法實現(xiàn)篇之并發(fā)標(biāo)記清除
這篇文章主要為大家介紹了GC算法實現(xiàn)篇之并發(fā)-標(biāo)記-清除,?CMS垃圾收集器在減少停頓時間上做了很多給力的工作,?大量的并發(fā)線程執(zhí)行的工作并不需要暫停應(yīng)用線程2022-01-01java多線程開發(fā)之通過對戰(zhàn)游戲?qū)W習(xí)CyclicBarrier
這篇文章給大家分享了關(guān)于java多線程開發(fā)中通過對戰(zhàn)游戲?qū)W習(xí)CyclicBarrier的相關(guān)知識點內(nèi)容,有興趣的朋友們學(xué)習(xí)參考下。2018-08-08淺談Java中Int、Integer、Integer.valueOf()、new Integer()之間的區(qū)別
本文主要介紹了淺談Java中Int、Integer、Integer.valueOf()、new Integer()之間的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11