文字解释:透明地跟踪谁创建或更改了实体以及创建、更改发生的时间。
代码解释:
// 模拟场景:修改文章标题和内容
Article article = new Article();
article.setId(1);
article.setTitle("new title");
article.setContent("new content");
// 手动设置修改日期
article.setModifyDate(LocalDateTime.now());
// 手动设置修改人员,此处按用户名
article.setModifyOperator("jack");
ArticleRepository.save(article);
启用审计功能后,代码将减少2行
// 修改文章标题和内容
Article article = new Article();
article.setId(1);
article.setTitle("new title");
article.setContent("new content");
ArticleRepository.save(article);
此场景需在实体类2个字段上分别添加2个注解:创建日期字段添加 @CreatedDate、修改日期字段添加 @LastModifiedDate,然后在类上添加 @EntityListeners 注解
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Article implements Serializable {
// 无关代码略
@CreatedDate
private LocalDateTime createTime;
@LastModifiedDate
private LocalDateTime updateTime;
}
数据类型选用 LocalDateTime,当然 Date、 Instant 等均可
最后在启动类或配置类上添加 @EnableJpaAuditing 注解
注解名已经说明了作用,实体监听,具体作用是:对标有此注解的实体类的 CRUD 操作进行监听(查询操作也是可以审计的)。该注解仅有一个 Class 数组类型的 value 属性,用于指定监听器类,上述代码中使用了 AuditingEntityListener 这个由 JPA 提供的监听器,审计功能实际就是通过该类中的方法奏效的
@PrePersist
public void touchForCreate(Object target) {
Assert.notNull(target, "Entity must not be null");
if (this.handler != null) {
AuditingHandler object = (AuditingHandler)this.handler.getObject();
if (object != null) {
object.markCreated(target);
}
}
}
@PreUpdate
public void touchForUpdate(Object target) {
Assert.notNull(target, "Entity must not be null");
if (this.handler != null) {
AuditingHandler object = (AuditingHandler)this.handler.getObject();
if (object != null) {
object.markModified(target);
}
}
}
不难看出,新增操作会执行 touchForCreate 方法,更新操作会调用 touchForUpdate 方法
查看该类结构可以发现,JPA 提供的 AuditingEntityListener 监听器仅监听了CRUD 中的 C新增、U更新,并未监听D删除操作,而且代码是固定的,无法整合业务,这就需要定制审计监听器了,方法见后文
同上,创建者字段添加 @CreatedBy 注解,修改者字段添加 @LastModifiedBy 字段
数据类型选用 String,用于保存被跟踪者的用户名。也可以为 Long(保存ID)、对象
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Article implements Serializable {
// 无关代码略
@CreatedBy
private String createOperator;
@LastModifiedBy
private String modifyOperator;
}
在此基础上,审计基础设施需要以某种方式了解当前主体,为此,JPA提供了一个 SPI 接口: AuditorAware(Servlet 模式,WebFlux 模式则为 ReactiveAuditorAware),以告知基础架构当前与应用程序交互的用户或系统是谁。通用泛型 T 定义了标有 @CreatedBy 或 @LastModifiedBy 的属性必须是什么类型。
@EnableJpaAuditing
@Configuration(proxyBeanMethods = false)
public class JpaConfig {
@Bean
public AuditorAware<String> auditorProvider() {
// TODO 获取当前主体 do something...
return () -> Optional.of("username");
}
}
泛型 T 必须和标有 @CreatedBy、@LastModifiedBy 属性的类型一致,当然这两个属性的类型也要一致
@Component
public class CustomAuditorAware implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
// TODO 获取当前主体 do something...
return Optional.of("username");
}
}
Spring Security 中提供了 SecurityContextHolder.getContext() 方法用于获取当前主体
@Component
public class SpringSecurityAuditorAware implements AuditorAware<User> {
@Override
public Optional<User> getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast);
}
}
当然,编写上述代码的前提是将 createOperator、modifyOperator 属性的类型设为 User。
上文的实体类为 Article ,故监听器命名为 ArticleAuditingListener。编码前还需要了解几个 Hibernate 支持的回调注解,其中2个也是上文未解释的
注解 | 描述 |
---|---|
@PostLoad | 在将实体加载到当前持久性上下文或刷新实体后执行。 |
@PostPersist | 在实体管理器持久化操作实际执行或级联后执行。在执行数据库 INSERT 后调用此调用。 |
@PostUpdate | 在数据库更新操作之前执行。 |
@PostRemove | 在实体管理器删除操作实际执行或级联后执行。此调用与删除操作同步。 |
以上均为 @Target({ElementType.METHOD}),只能标在方法上
一句话概括,执行时机分别对应实体的 CRUD 操作,Post 表示后执行,与之对应的 @PreXxx 注解同理,表格不再列出,需要注意一点,不存在@PreLoad 注解,因为逻辑上无意义,故一共有7个回调注解
开始编码
@Slf4j
public class ArticleAuditingListener {
@PostLoad
private void postLoad(Article entity) {
log.info("查询后做些什么 {}", entity);
}
@PostPersist
private void postPersist(Article entity) {
log.info("插入后做些什么 {}", entity);
}
@PostUpdate
private void postUpdate(Article entity) {
log.info("更新后做些什么 {}", entity);
}
@PostRemove
private void postRemove(Article entity) {
log.info("删除后做些什么 {}", entity);
}
// 其余3个注解略,同理
}
之后在 @EntityListeners 中配置
@Entity
@EntityListeners({AuditingEntityListener.class, ArticleAuditingListener.class})
public class Article implements Serializable {
// 无关代码略
@CreatedDate
private LocalDateTime createTime;
@LastModifiedDate
private LocalDateTime updateTime;
}
还有一个方便的基类 AbstractAuditable,您可以扩展它以避免手动实现接口方法的需要。但这样做会增加实体类与 Spring Data 的耦合,这可能是您想要避免的事情。通常,首选基于注释的方式来定义审计元数据,因为它侵入性更小且更灵活。
新建实体类 Customer,继承 AbstractAuditable
U 表示创建者和修改者字段的类型,必须为引用类型,因为这两个字段被 @ManyToOne 标记
PK 表示主键的类型
最终结果
@Data
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Customer extends AbstractAuditable<Customer, Long> {
// 省略了 id、createdBy、createdDate、lastModifiedBy、lastModifiedDate,因为已被继承
private String name;
}
由于 U 指定为 Customer,因此 AuditorAware 中的 T 也要指定为 Customer
@EnableJpaAuditing
@Configuration(proxyBeanMethods = false)
public class JpaConfig {
@Autowired
CustomerRepository customerRepository;
/* @Bean
public AuditorAware auditorProvider() {
return () -> Optional.of("pcdd");
} */
@Bean
public AuditorAware<Customer> auditorProvider() {
// 这里需要确保 id=1 的记录在程序运行前就已在 customer 表中
return () -> customerRepository.findById(1L);
}
}
之前定义的Bean需要被替换,只能保留一个,上文的 Article 实体类的2个字段也要做兼容处理,类型修改为 Customer ,这也说明 JPA 项目的审计字段类型要保持类型统一
观察 JPA 生成的 Customer 表结构,发现 createdBy、lastModifiedBy 字段被设为外键了,实际存储的是 customer 的 ID
create table customer
(
id bigint not null
primary key,
created_date datetime(6) null,
last_modified_date datetime(6) null,
name varchar(255) null,
created_by_id bigint null,
last_modified_by_id bigint null,
constraint FK1m3j3p1e2rd5ppt4wpwqjv5rh
foreign key (last_modified_by_id) references customer (id),
constraint FKitafsntr2a6dfn48t56h73puw
foreign key (created_by_id) references customer (id)
);
最后使用 CustomerRepository 进行 CRUD 操作,观察审计效果
通过上面的说明,总结 AbstractAuditable 的优缺点
优点:节省代码,让项目中的所有实体类均继承该类(类似 BaseEntity),可以省去定义5个字段:id、createdBy、createdDate、lastModifiedBy、lastModifiedDate
缺点:
文章链接:https://blog.csdn.net/weixin_43553153/article/details/130017612