当系统中有审计需求时,特别是需要对某些数据进行动态监控时,我们可以使用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;
}

然后使用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.创建数据表

----------------------分割线------------------------------

看一下效果:

spring data JPA 使用EntityentiListeners实现数据审计功能设计的更多相关文章

  1. Spring Data JPA的Audit功能,审计数据库的变更

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 数据库审计 数据库审计是指当数据库有记录变更时,可以记录数据库的变更时间和变更人等,这样以后出问题回溯问责也比较方便. ...

  2. 整合Spring Data JPA与Spring MVC: 分页和排序

    之前我们学习了如何使用Jpa访问关系型数据库.比较完整Spring MVC和JPA教程请见Spring Data JPA实战入门,Spring MVC实战入门. 通过Jpa大大简化了我们对数据库的开发 ...

  3. 干货|一文读懂 Spring Data Jpa!

    有很多读者留言希望松哥能好好聊聊 Spring Data Jpa!其实这个话题松哥以前零零散散的介绍过,在我的书里也有介绍过,但是在公众号中还没和大伙聊过,因此本文就和大家来仔细聊聊 Spring D ...

  4. Spring Data JPA(官方文档翻译)

    关于本书 介绍 关于这本指南 第一章 前言 第二章 新增及注意点 第三章 项目依赖 第四章 使用Spring Data Repositories 4.1 核心概念 4.2 查询方法 4.3 定义rep ...

  5. Spring Boot2 系列教程(二十三)理解 Spring Data Jpa

    有很多读者留言希望松哥能好好聊聊 Spring Data Jpa! 其实这个话题松哥以前零零散散的介绍过,在我的书里也有介绍过,但是在公众号中还没和大伙聊过,因此本文就和大家来仔细聊聊 Spring ...

  6. 整合Spring Data JPA与Spring MVC: 分页和排序pageable

    https://www.tianmaying.com/tutorial/spring-jpa-page-sort Spring Data Jpa对于分页以及排序的查询也有着完美的支持,接下来,我们来学 ...

  7. Spring MVC和Spring Data JPA之获取数据表数据放在List集合,显示在JSP页面

    涉及到很多xml配置没写:只写具体实现的所有类 1.实体类 对应数据表SYS_SBGL, 主键是SBBM,主键是自动生成的uuid 数据表内容如下(有图有真相): package com.jinhet ...

  8. springboot集成Spring Data JPA数据查询

    1.JPA介绍 JPA(Java Persistence API)是Sun官方提出的Java持久化规范.它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据.它的出现主要是为 ...

  9. 使用Spring Data JPA进行数据分页与排序

    一.导读 如果一次性加载成千上万的列表数据,在网页上显示将十分的耗时,用户体验不好.所以处理较大数据查询结果展现的时候,分页查询是必不可少的.分页查询必然伴随着一定的排序规则,否则分页数据的状态很难控 ...

随机推荐

  1. JDK、JRE、JVM,是什么关系?

    作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...

  2. js中点回车enter触发事件&layui弹窗按enter键不停弹窗问题的解决&js实现鼠标焦点自动落到文本框(layui)

     js中回车触发事件 一. document.onkeydown = function (e) { // 回车提交表单 // 兼容FF和IE和Opera var theEvent = window.e ...

  3. C# 将json字符串进行排序 转成键值

    public static string StortJson(string json) { var dic = JsonConvert.DeserializeObject<SortedDicti ...

  4. 10天,从.Net转Java,并找到月薪2W的工作(二)

    辞去.Net工作之后,第一天直接去星巴克学习. 研究如何入门Java,对比学习资料以及安装Ieda. 由于正版太贵,Mac又不容易破解.鼓捣半天,最后结果是,我决定用教育账号申请一年的免费IDEA. ...

  5. [LeetCode]319. Bulb Switcher灯泡开关

    智商压制的一道题 这个题有个数学定理: 一般数(非完全平方数)的因子有偶数个 完全平凡数的因子有奇数个 开开关的时候,第i个灯每到它的因子一轮的时候就会拨动一下,也就是每个灯拨动的次数是它的因子数 而 ...

  6. Java Hash表 数据结构

    思考: 数组由于内存地址连续,是一种查询快增删慢的数据结构: 链表由于内存地址不连续,是一种查询慢增删快的数据结构: 那么怎么实现查询又快,增删也快的数据结构呢? 要是把数组和链表结合起来会怎么样? ...

  7. spring乱码处理

    在web.xml添加post乱码filter:CharacterEncodingFilter 2). 对于get请求中文参数出现乱码解决方法有两个: a. 修改tomcat配置文件添加编码与工程编码一 ...

  8. 多线程并行_countDown

    /** * 首次启动加载数据至缓存 */ public class ApplicationStartTask { private static Logger logger = LoggerFactor ...

  9. WebService 适用场合

    适用场合 1.跨防火墙通信 如果应用程序有成千上万的用户,而且分布在世界各地,那么客户端和服务器之间的通信将是一个棘手的问题.因为客户端和服务器之间通常会有防火墙或者代理服 务器.在这种情况下,使用D ...

  10. Python基础(中篇)

    本篇文章主要内容:数据类型的常用方法,条件语句,循环语句. 在开始正篇之前我们先来看看上一篇留下的题目. 题目: 定义一个字典a,有两个键值对:一个键值对key是可乐,value是18:另一个键值对k ...