Auditing 及其事件详解

Auditing 翻译过来是审计和审核,Spring 的优秀之处在于帮我们想到了很多繁琐事情的解决方案,我们在实际的业务系统中,针对一张表的操作大部分是需要记录谁什么时间创建的,谁什么时间修改的,并且能让我们方便的记录操作日志。Spring Data JPA 为我们提供了审计功能的架构实现,提供了四个注解专门解决这件事情:

  • @CreatedBy 哪个用户创建的。
  • @CreatedDate 创建的时间。
  • @LastModifiedBy 修改实体的用户。
  • @LastModifiedDate 最后一次修改时间。

Auditing 如何配置

我们以一个快速的例子,看看它是怎么配置生效的。

(1)先新建一个 @Entity:UserCustomerEntity 里面的写法如下。

@Entity
@Table(name = "user_customer", schema = "test", catalog = "")
@EntityListeners(AuditingEntityListener.class)
public class UserCustomerEntity {@Id@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;@CreatedDate@Column(name = "create_time", nullable = true)private Date createTime;@CreatedBy@Column(name = "create_user_id", nullable = true)private Integer createUserId;@LastModifiedBy@Column(name = "last_modified_user_id", nullable = true)private Integer lastModifiedUserId;@LastModifiedDate@Column(name = "last_modified_time", nullable = true)private Date lastModifiedTime;@Column(name = "customer_name", nullable = true, length = 50)private String customerName;@Column(name = "customer_email", nullable = true, length = 50)private String customerEmail;
......
}

@Entity 实体中我们需要做两点:

  • 相应的字段添加 @CreatedBy、@CreatedDate、@LastModifiedBy and @LastModifiedDate注解。
  • 增加 @EntityListeners(AuditingEntityListener.class)。

(2)实现 AuditorAware 接口告诉 JPA 当前的用户是谁。

实现 AuditorAware 接口,实现 getCurrentAuditor 方法,返回一个 Integer 的 user ID。以下代码介绍了两种做法:

public class MyAuditorAware implements AuditorAware<Integer> {/*** Returns the current auditor of the application.* @return the current auditor*/@Overridepublic Integer getCurrentAuditor() {
//    第一种方式:如果我们集成了spring的Security,我们直接通过如下方法即可获得当前请求的用户ID.
//    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//    if (authentication == null || !authentication.isAuthenticated()) {
//       return null;
//    }
//    return ((LoginUserInfo) authentication.getPrincipal()).getUser().getId();//第二种方式通过request里面取或者session里面取ServletRequestAttributes servletRequestAttributes =
(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();return (Integer) servletRequestAttributes.getRequest().getSession().getAttribute("userId");}
}

而 AuditorAware 的源码如下:

public interface AuditorAware<T> {T getCurrentAuditor();
}

通过实现 AuditorAware 接口的 getCurrentAuditor() 方法告诉 JPA 当前的用户是谁,里面实现方法千差万别,作者举例了两种最常见的:

  • 通过 Security 取。
  • 通过 Request 取。

(3)通过 @EnableJpaAuditing 注解开启 JPA 的 Auditing 功能。

并且告诉应用 AuditorAware 的实现类是谁,也就是我们通过 @Bean 注解把上面的实现类放到 Spring 的 Bean 管理里面,当然了也可以上面的类加上 @Component。具体配置方式如下:

@SpringBootApplication
@EnableJpaAuditing
public class QuickStartApplication {public static void main(String[] args) {SpringApplication.run(QuickStartApplication.class, args);}@Beanpublic AuditorAware<Integer> auditorProvider() {return new MyAuditorAwareImpl();}
}

验证结果如下。

通过以上的三步,我们已经完成了 auting 的配置,通过 userCustomerRepository.save(new UserCustomerEntity("1","Jack")); 的执行,我们看数据库里面的 4 个字段已经给填上去了。

@MappedSuperclass

实际工作中我们还会对上面的实体部分进行改进,引入 @MappedSuperclass 注解,我们将 @Id、@CreatedBy、@CreatedDate、@LastModifiedBy and @LastModifiedDate 抽象到一个公用的基类里面,方便公用和形成每个表的字段约束。可以将其放到我们公司的框架代码上,对表设计形成统一的强约束。

步骤如下:

(1)改进后我们新增一个 AbstractAuditable 的抽象类:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditable {@Id@Column(name = "id", nullable = false)@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;@CreatedDate@Column(name = "create_time", nullable = true)private Date createTime;@CreatedBy@Column(name = "create_user_id", nullable = true)private Integer createUserId;@LastModifiedBy@Column(name = "last_modified_user_id", nullable = true)private Integer lastModifiedUserId;@LastModifiedDate@Column(name = "last_modified_time", nullable = true)private Date lastModifiedTime;
......
}

(2)而我们每个需要 Auditing 的实体只需要继承 AbstractAuditable 即可。

内容如下:

@Entity
@Table(name = "user_customer", schema = "test", catalog = "")
public class UserCustomerEntity extends AbstractAuditable {@Column(name = "customer_name", nullable = true, length = 50)private String customerName;@Column(name = "customer_email", nullable = true, length = 50)private String customerEmail;
......}

Auditing 原理解析

(1)我们先看一下关键的几个源码的关系图:

(2)AuditingEntityListener 的源码如下:

@Configurable
public class AuditingEntityListener {private ObjectFactory<AuditingHandler> handler;public void setAuditingHandler(ObjectFactory<AuditingHandler> auditingHandler) {Assert.notNull(auditingHandler, "AuditingHandler must not be null!");this.handler = auditingHandler;}//在新增之前通过handler来往我们的@Entity里面的auditor的那些字段塞值。@PrePersistpublic void touchForCreate(Object target) {if (handler != null) {handler.getObject().markCreated(target);}}//在更新之前通过handler来往我们的@Entity里面的auditor的那些字段塞值。@PreUpdatepublic void touchForUpdate(Object target) {if (handler != null) {handler.getObject().markModified(target);}}
}

(3)通过调用关系图和 AuditingEntityListener,我们其实可以发现以下两点情况:

  • AuditingEntityListener 通过委托设计模式,委托 AuditingHandler 进行处理,而我们看 AuditingHandler 的源码会发现,里面就是根据 ID 和 Version(后面介绍)来判断我们的对象是新增还是更新,从而来更改时间字段和 User 字段。而 User 字段是通过 AuditorAware 的实现类来取的,并且 AuditorAware 没有默认实现类,只有我们自己的实现类,也就是 AuditorAware 的实现类必须我们自己来定义,否则启动会报错。
  • AuditingEntityListener 的代码如此简单,我们能不能自定义呢?答案是肯定的,通过 @PrePersist、@PreUpdate 查看源码得出,Java Persistence API 底层又帮我们提供的 Callbacks,而这些回调方法,用于侦听保存、查询(抓取)、更新和删除数据库中的数据。注解方式如下:

Type

描述

@PrePersist

新增之前

@PreRemove

删除之前

@PostPersist

新增之后

@PostRemove

删除之后

@PreUpdate

更新之前

@PostUpdate

更新之后

@PostLoad

加载后

注意:这个方法都是同步机制,一但报错将会影响所有底层代码执行。在实际工作中实现这些方法的时候,方法体里面开启异步线程,或者消息队列,来异步处理日志,或者更繁重的工作。

Listener 事件的扩展

自定义 EntityListener

随着 DDD 的设计模式逐渐被大家认可和热捧,JPA 通过这种 Listener 这种机制可以很好的实现事件分离、状体分离。假如,订单的状态变化可能对我们来说比较重要,我们需要定一个类去监听订单状态变更,通知相应的逻辑代码各自去干各自的活。

(1)新增一个 OrderStatusAuditListener 类,在相应的操作上添加 Callbacks 注解。

public class OrderStatusAuditListener {@PostPersistprivate void postPersist(OrderEntiy entity) {//当更新的时候做一些逻辑判断,及其事件通知。}@PostRemoveprivate void PostRemove(OrderEntiy entity) {//当删除的时候做一些逻辑判断。}@PostUpdate private void PostUpdate(OrderEntiy entity) {//当更新的时候// entity.getOrderStatus(),做一些逻辑判断}
}

(2)我们的订单实体变化如下:

@Entity
@Table("orders")
@EntityListeners({AuditingEntityListener.class, OrderStatusAuditListener.class})
public class OrderEntity  extends AbstractAuditable{@Enumerated(EnumType.STRING)@Column("order_status")private OrderStatusEnum orderStatus;......
}

即可完成自定义 EntityListener。

实际工作记录操作日志的实例

public class ActionsLogsAuditListener {private static final Logger logger = LoggerFactory.getLogger(ActionsLogsAuditListener.class);@PostLoadprivate void postLoad(Object entity) {this.notice(entity, OperateType.load);}@PostPersistprivate void postPersist(Object entity) {this.notice(entity, OperateType.create);}@PostRemoveprivate void PostRemove(Object entity) {this.notice(entity, OperateType.remove);}@PostUpdateprivate void PostUpdate(Object entity) {this.notice(entity, OperateType.update);}private void notice(Object entity, OperateType type) {logger.info("{} 执行了 {} 操作", entity, type.getDescription());//我们通过active mq 异步发出消息处理事件ActiveMqEventManager.notice(new ActiveMqEvent(type, entity));}enum OperateType {create("创建"), remove("删除"),update("修改"),load("查询");private final String description;OperateType(String description) {this.description=description;}public String getDescription() {return description;}}
}

我们通过自定义的 ActionsLogsAuditListener 来监听我们要处理日志的实体,然后将事件变更,通过消息队列进行异步处理,这样就可以完全解耦了。当然了,这里我们解耦的方式也可以通过 Spring 的事件机制进行解决。通过工作中的此示例,来帮助大家更好的理解 Audit 的机制,顺便说一下处理操作的日志的正确思路,记录当前真实发生的数据和状态,及其时间即可,具体变化了什么那是在业务展示层面上要做的事情,这里没有必要做比对的事情,记住这一点之后就会让你的日志处理实现机制豁然明朗,变得容易许多。

Spring Data JPA 从入门到精通~Auditing及其事件详解相关推荐

  1. Spring Data JPA 从入门到精通~Naming命名策略详解及其实践

    Naming 命名策略详解及其实践 用 JPA 离不开 @Entity 实体,我都知道实体里面有字段映射,而字段映射的方法有两种: 显式命名:在映射配置时,设置的数据库表名.列名等,就是进行显式命名, ...

  2. Spring data JPA 之 Jackson 在实体里面的注解详解

    8 Spring data JPA 之 Jackson 在实体里面的注解详解 经过前⾯课时的讲解,相信你已经对实体⾥⾯的 JPA 注解有了⼀定的了解,但是实际⼯作中你会发现实体⾥⾯不仅有 JPA 的注 ...

  3. Spring Data JPA 从入门到精通~@Version处理乐观锁的问题

    @Version 处理乐观锁的问题 @Version 乐观锁介绍 我们在研究 Auditing 的时候,发现了一个有趣的注解 @Version,源码如下: package org.springfram ...

  4. spring data jpa从入门到精通_Spring Data JPA的简单入门

    前言 spring data JPA是spring团队打造的sping生态全家桶的一部分,本身内核使用的是hibernate核心源码,用来作为了解java持久层框架基本构成的样本是再好不过的选择.最近 ...

  5. Spring Data JPA 从入门到精通~默认数据源的讲解

    默认数据源 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://1 ...

  6. Spring Data JPA 从入门到精通~实际工作的应用场景

    在实际工作中,有哪些场景会用到自定义 Repository 呢,这里列出几种实际在工作中的应用案例. 1. 逻辑删除场景 可以用到上面说的两种实现方式,如果有框架级别的全局自定义 Respositor ...

  7. Spring Data JPA 从入门到精通~自定义实现Repository

    EntityManager 的获取方式 我们既然要自定义,首先讲一下 EntityManager 的两种获取方式. 1. 通过 @PersistenceContext 注解. 通过将 @Persist ...

  8. Spring Data JPA 从入门到精通~EntityManager介绍

    EntityManager 介绍 我们前面已经无数次提到了,JPA 的默认 Repository 的实现类是 SimpleJpaRepository,而里面的具体实现就是调用的 EntityManag ...

  9. Spring Data JPA 从入门到精通~JpaSpecificationExecutor示例

    新建两个实体 @Entity(name = "UserInfoEntity") @Table(name = "user_info", schema = &quo ...

最新文章

  1. iOS开发系列--UITableView全面解析
  2. 信息系统项目管理师-项目整体、收尾、变更管理考点笔记
  3. [转载] 如何在Android设备之间共享Google Play应用,音乐等
  4. Mysql学习总结(70)——MySQL 优化实施方案
  5. c#数据类型的值传递和引用传递--基础拾遗
  6. MySQL复制应用中继日志解析
  7. Node rabbitmq 入门就够了
  8. 如何避免_如何避免钢板弹簧受损
  9. u盘无法格式化不在计算机中,u盘被写保护无法格式化怎么办 u盘无法格式化原因及解决...
  10. dns备用服务器信息,dns服务器地址(dns首选和备用填多少)
  11. 计算机资源管理器总是未响应,资源管理器总是无响应,而且开机很慢老是解决不了问题...
  12. Python列表排序_revered逆序_max_min_sum
  13. Scene之间的数据传递
  14. Dockerfile 的详解
  15. TypeError [ERR_INVALID_ARG_TYPE]: The “path“ argument must be of type string. Received undefined
  16. 5G 频段 频率与Band对应表
  17. Android Studio开发APP
  18. 【Beetl笔记整理四】表达式
  19. 机器学习入门(二)一元线性回归
  20. RFID无线射频识别技术基本介绍

热门文章

  1. 论文浅尝 - AAAI2020 | 从异质外部知识库中进行基于图的推理实现常识知识问答...
  2. 新书速递 | 《知识图谱:方法、实践与应用》
  3. 领域应用 | 知识图谱数据构建的“硬骨头”,阿里工程师如何拿下?
  4. Android官方开发文档Training系列课程中文版:手势处理之ViewGroup的事件管理
  5. 二、python框架相关知识体系
  6. CSS计数器(自定义列表)
  7. Android——检查网络是否已经链接
  8. signed 与 unsigned 有符号和无符号数
  9. SpringMVC核心——视图渲染(包含视图解析)问题
  10. C1. 组队活动 Small(BNUOJ)