spring data JPA 使用EntityentiListeners實現數據審計功能設計


當系統中有審計需求時,特別是需要對某些數據進行動態監控時,我們可以使用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;
    }
View Code

注意:這里存在事務問題,如果直接使用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;

    }
View Code

 

然后使用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);
      }
View Code

 

代碼實現:

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);

    }

    

}
View Code

2.為實體添加監控注解:@EntityListeners({AuditingEntityListener.class,QuotationStausListener.class})

  重寫equals方法,使用 eclipse自帶generator生成即可

3.創建數據表

----------------------分割線------------------------------

看一下效果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM