Spring Data Envers支持有條件變動(dòng)紀(jì)錄的保存和查詢的方法
數(shù)據(jù)審計(jì)是業(yè)務(wù)系統(tǒng)的一個(gè)基本能力,需要系統(tǒng)能夠?qū)㈥P(guān)鍵數(shù)據(jù)的變動(dòng)紀(jì)錄都保存下來(lái),并支持變動(dòng)紀(jì)錄的查詢。
通過(guò)spring-data-envers可以很容易的實(shí)現(xiàn)數(shù)據(jù)變動(dòng)紀(jì)錄的保存和查詢。
有些情況下,我們需要只保存滿足特定條件的數(shù)據(jù)變動(dòng)紀(jì)錄,不滿足條件的變動(dòng)紀(jì)錄不進(jìn)行保存,例如只保存某個(gè)字段有值的變動(dòng)紀(jì)錄。
本文介紹支持有條件變動(dòng)紀(jì)錄的保存和查詢的方法。
具體的代碼參照 示例項(xiàng)目 https://github.com/qihaiyan/springcamp/tree/master/spring-data-envers-conditional
一、概述
可以通過(guò) spring-data-envers 很容易的實(shí)現(xiàn)變動(dòng)紀(jì)錄的保存和查詢,只需要增加幾個(gè)注解就可以。但是要實(shí)現(xiàn)有條件的變動(dòng)紀(jì)錄的保存和查詢就需要進(jìn)行一些復(fù)雜的處理。
二、使用 spring-data-envers
首先引入 spring-data-envers 依賴。
在 build.gradle 中增加一行代碼:
implementation 'org.springframework.data:spring-data-envers'
在實(shí)體類上增加 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> { }
通過(guò)以上3步操作,就添加好了變動(dòng)紀(jì)錄的保存功能,我們可以通過(guò)調(diào)用變動(dòng)紀(jì)錄查詢方法確認(rèn)變動(dòng)紀(jì)錄保存成功。
當(dāng) Repository 擴(kuò)展 RevisionRepository 方法后,會(huì)有一個(gè)默認(rèn)實(shí)現(xiàn)的 findRevisions 方法,我們可以直接調(diào)用:
public Revisions<Integer, MyData> findRevisions(Long id) { return myDataRepository.findRevisions(id); }
最后我們可以執(zhí)行完整的主體數(shù)據(jù)的保存,在控制臺(tái)中打印變動(dòng)紀(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ù)的操作都可以查詢到對(duì)應(yīng)的變動(dòng)紀(jì)錄,并且變動(dòng)紀(jì)錄還通過(guò) 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}
三、通過(guò)自定義 Event Listener 實(shí)現(xiàn)有條件的變動(dòng)紀(jì)錄的保存
在進(jìn)行數(shù)據(jù)變動(dòng)時(shí), Envers 通過(guò)監(jiān)聽(tīng)事件來(lái)進(jìn)行對(duì)應(yīng)的處理,總共有以下幾個(gè)監(jiān)聽(tīng)事件:
EventType.POST_INSERT EventType.PRE_UPDATE EventType.POST_UPDATE EventType.POST_DELETE EventType.POST_COLLECTION_RECREATE EventType.PRE_COLLECTION_REMOVE EventType.PRE_COLLECTION_UPDATE
每個(gè)監(jiān)聽(tīng)事件都對(duì)應(yīng)著特定的 Listener ,在本文實(shí)例中,我們期望當(dāng) author 的值被更新為空時(shí),不保存變動(dòng)紀(jì)錄,我們可以通過(guò)自定義 PRE_UPDATE 和 POST_UPDATE 的Listener來(lái)實(shí)現(xiàn)。
因?yàn)榭蚣芴峁┝四J(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 中,我們?cè)黾恿?對(duì)于 author 字段是否為空的判斷邏輯。
四、自定義 Event Listener 注冊(cè)到系統(tǒng)中
自定義 Event Listener 完成后,我們還需要讓框架執(zhí)行我們自定義的 Listener, 而不是用默認(rèn)的 Listener。
框架通過(guò) EnversIntegrator 類注冊(cè)的 Listener, 我們要做的是重新實(shí)現(xiàn) EnversIntegrator , 在本實(shí)例中重新實(shí)現(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 } }
通過(guò)代碼可以發(fā)現(xiàn),我們只是修改了 PRE_UPDATE 和 POST_UPDATE 注冊(cè)的 Listener , 其它事件的 Listener 仍然用框架默認(rèn)的。
最后我們需要把我們實(shí)現(xiàn)的 MyEnversIntegrator 放到 META-INF/services/org.hibernate.integrator.spi.Integrator 這個(gè)配置文件中。
cn.springcamp.springdata.envers.MyEnversIntegrator
五、確認(rèn)有條件變動(dòng)紀(jì)錄的保存是否生效
最后我們修改控制臺(tái)打印程序,將 author 字段更新為 null 并保存,查看變動(dòng)紀(jì)錄里是否有這個(gè)更新操作的紀(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í)行程序并觀察控制臺(tái)打印內(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}
通過(guò)打印內(nèi)容可以確認(rèn),author 字段更新為 null 的變動(dòng)紀(jì)錄沒(méi)有被紀(jì)錄,說(shuō)明我們的處理是生效的。
到此這篇關(guān)于Spring Data Envers支持有條件變動(dòng)紀(jì)錄的保存和查詢的方法的文章就介紹到這了,更多相關(guān)Spring Data Envers內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java注解結(jié)合aspectj AOP進(jìn)行日志打印的操作
這篇文章主要介紹了java注解結(jié)合aspectj AOP進(jìn)行日志打印的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02java中統(tǒng)一返回前端格式及統(tǒng)一結(jié)果處理返回詳解
這篇文章主要介紹了統(tǒng)一結(jié)果處理的重要性,以及如何在SpringBoot項(xiàng)目中定義和使用統(tǒng)一結(jié)果返回類,通過(guò)構(gòu)造器私有化和靜態(tài)方法ok、error,提供了成功和失敗的統(tǒng)一響應(yīng)格式,需要的朋友可以參考下2025-02-02SpringBoot+JavaMailSender實(shí)現(xiàn)騰訊企業(yè)郵箱配置
這篇文章主要介紹了SpringBoot+JavaMailSender實(shí)現(xiàn)騰訊企業(yè)郵箱配置,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04GC算法實(shí)現(xiàn)篇之并發(fā)標(biāo)記清除
這篇文章主要為大家介紹了GC算法實(shí)現(xiàn)篇之并發(fā)-標(biāo)記-清除,?CMS垃圾收集器在減少停頓時(shí)間上做了很多給力的工作,?大量的并發(fā)線程執(zhí)行的工作并不需要暫停應(yīng)用線程2022-01-01java多線程開(kāi)發(fā)之通過(guò)對(duì)戰(zhàn)游戲?qū)W習(xí)CyclicBarrier
這篇文章給大家分享了關(guān)于java多線程開(kāi)發(fā)中通過(guò)對(duì)戰(zhàn)游戲?qū)W習(xí)CyclicBarrier的相關(guān)知識(shí)點(diǎn)內(nèi)容,有興趣的朋友們學(xué)習(xí)參考下。2018-08-08淺談Java中Int、Integer、Integer.valueOf()、new Integer()之間的區(qū)別
本文主要介紹了淺談Java中Int、Integer、Integer.valueOf()、new Integer()之間的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11