當系統中有審計需求時,特別是需要對某些數據進行動態監控時,我們可以使用EntityentiListeners來實現,當然這是基於使用JPA而不是mybatis的情況下。
當前我們的需求場景:
1.需要監控某一個實體的數據變化(add,update,delete)
2.需要記錄:id,who,when, action, entity,condition,value分別表示id,操作人,操作時間,動作(add,update,delete),實體名稱,狀態(before:操作前,after:操作后),值
如何做?
1.如何識別add,update,delete操作?
entityListenners有定義的@PreUpdate:更新前,@PostUpdate:更新后,@PrePersist:保存前,@PostPersist:保存后,@PreRemove:刪除前。
我們可以在保存前獲取保存的數據,記錄為add新增操作數據,我們在刪除前獲取數據,記錄為刪除的數據,我們在更新前,獲取當前數據為更新后的數據,另外根據id從數據庫獲取之前的數據作為之前的數據,此時加以對比,若存在差異則為更新。
ps:如何區分update和add?updata時實體中是存在id的,add時不存在。
2.如何獲取當前操作人:entityListenner和AOP有點不一樣,需要在啟動類中加上下列語句:

@Bean public MethodInvokingFactoryBean methodInvokingFactoryBean() { MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean(); methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class); methodInvokingFactoryBean.setTargetMethod("setStrategyName"); methodInvokingFactoryBean.setArguments(new String[]{SecurityContextHolder.MODE_INHERITABLETHREADLOCAL}); return methodInvokingFactoryBean; }
注意:這里存在事務問題,如果直接使用resposity查詢數據不對,應該這樣:
Quotation oldData = quotationService.findOldData(quotation.getId());
findOldData方法:

//@Transactional(propagation=Propagation.REQUIRES_NEW) --不管是否存在事務,都創建一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務 @Transactional(propagation = Propagation.REQUIRES_NEW) public Quotation findOldData(Long id) { Optional<Quotation> optional = quotationRepository.findById(id); if (optional.isPresent()) { return optional.get(); } return null; }
然后使用spring security工具獲取當前人,類似這樣(具體根據本系統具體情況定制):

public Optional<String> getCurrentAuditor() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String reString=(String)((JwtAuthenticationToken)authentication).getToken().getClaims().get("preferred_username"); return Optional.of(reString); }
代碼實現:
1.添加自己的listener類,實現監控,存日志邏輯

package com.b.pos.quotation.listeners; import java.time.Instant; import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.persistence.PostPersist; import javax.persistence.PostUpdate; import javax.persistence.PrePersist; import javax.persistence.PreRemove; import javax.persistence.PreUpdate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.bmw.pos.quotation.domain.InterfaceLog; import com.bmw.pos.quotation.domain.Quotation; import com.bmw.pos.quotation.repository.InterfaceLogRepository; import com.bmw.pos.quotation.security.SecurityUtils; import com.bmw.pos.quotation.service.QuotationService; import com.bmw.pos.quotation.util.ProfileUtil; import com.google.common.base.Throwables; /** * Application status Listener */ @Component public class QuotationStausListener { private static final Logger log = LoggerFactory.getLogger(QuotationStausListener.class); private static InterfaceLogRepository interfaceLogRepository; private static QuotationService quotationService; @Autowired public synchronized void setInterfaceLogRepository(InterfaceLogRepository interfaceLogRepository) { QuotationStausListener.interfaceLogRepository = interfaceLogRepository; } @Autowired public synchronized void setQuotationService(QuotationService quotationService) { QuotationStausListener.quotationService = quotationService; } /** * after save success * * @param object */ @PostPersist public void postpersist(Object object) { } /** * after update success * * @param object */ @PostUpdate public void postUpdate(Object object) { } @PreRemove public void beforeRemove(Object object) { log.info("@PreRemove"); Quotation quotation = (Quotation) object; cachedThreadPool.execute(new Runnable() { @Override public void run() { saveInterfaceLog(quotation.getId().toString(), quotation.toString(),"Quotation","Delete","Before"); } }); } @PreUpdate public void beforeUpdate(Object object) { log.info("@PreUpdate"); try { Quotation quotation = (Quotation) object; log.info("@PreUpdate--------->Quotation: {}",quotation.toString()); Quotation oldData = quotationService.findOldData(quotation.getId()); log.info("oldData.get()------->oldData.get(): {}",oldData.toString()); if(oldData!=null&&(!quotation.equals(oldData))) { cachedThreadPool.execute(new Runnable() { @Override public void run() { saveInterfaceLog(quotation.getId().toString(), oldData.toString(),"Quotation","Update","Before"); saveInterfaceLog(quotation.getId().toString(), quotation.toString(),"Quotation","Update","After"); } }); } } catch (Exception e) { log.info(Throwables.getStackTraceAsString(e)); } } @PrePersist public void beforeSave(Object object) { log.info("@PrePersist"); Quotation quotation = (Quotation) object; cachedThreadPool.execute(new Runnable() { @Override public void run() { saveInterfaceLog(quotation.getId().toString(), quotation.toString(),"Quotation","Add",""); } }); } ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); private String getCurrentUserName() { Optional<String> user=SecurityUtils.getCurrentUserLogin(); return user.isPresent() ? user.get() : null; } /** * save log * * @param appNo * @param requestData * @param response * @param exceptions */ private void saveInterfaceLog(String quotationNo, String curData,String entityName,String action,String condition) { // save log InterfaceLog interfaceLog = new InterfaceLog(); interfaceLog.setIntType(entityName); interfaceLog.setReturnParam(curData); interfaceLog.setCalledTime(Instant.now()); String channel = ProfileUtil.getChannelByProfile(); interfaceLog.setEntityType(channel); interfaceLog.setAppGlobalId(quotationNo); interfaceLog.setEntityType(action); interfaceLog.setEntityCode(condition); interfaceLog.setInputParam(getCurrentUserName()); log.info("InterfaceLog: {}",interfaceLog.toString()); QuotationStausListener.interfaceLogRepository.saveAndFlush(interfaceLog); } }
2.為實體添加監控注解:@EntityListeners({AuditingEntityListener.class,QuotationStausListener.class})
重寫equals方法,使用 eclipse自帶generator生成即可
3.創建數據表
----------------------分割線------------------------------
看一下效果: