Jpa添加Audit
OriginalAbout 3 min
Spring中添加Audit
Spring中内置了一些跟Audit相关的注解,
@CreatedBy
: 表的这条记录是谁创建的,可以是人也可以是系统@CreatedDate
: 记录的创建时间@LastModifiedBy
: 表记录被谁更新@LastModifiedDate
: 表记录的更新时间,默认情况下创建的记录,该字段也会插入值
下面我们具体看下它们的SpringBoot中的使用,首先数据库和jpa
的依赖是首要条件。这里不多赘述
定义Audit的基类
@Setter
@Getter
@MappedSuperclass
@NoArgsConstructor
@SuperBuilder
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
// -1表示未删除,0表示物理删除, 1708874319这种表示逻辑删除
public static final String SOFT_DELETED_CLAUSE = "deleted_at = -1";
@Id
private Long id;
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String updatedBy;
@LastModifiedDate
private LocalDateTime updatedAt;
@Version
private Integer version;
private Long deletedAt = -1L;
}
@MappedSuperclass
: 表示这个类是非持久类,并且hibernate
将不会创建/查找此类的表映射@EntityListeners
: 定义Jpa事件的回调,比如当实体上发生指定事件(例如实体创建、更新或删除)时,将自动调用这些方法AuditingEntityListener
: Jpa内置的类,提供了对@CreatedBy, @LastModifiedBy, @CreatedDate, @LastModifiedDate这些注解的支持@Version
: 会在第一次插件的时候设置为0,后续的每次更新会叠加。主要是辅助实现乐观锁
添加
deletedAt
字段,主要是标记记录的删除状态。未删除、物理删除、逻辑删除
定义继承BaseEntity的Entity类
@Entity
@Getter
@NoArgsConstructor
@SuperBuilder
@Table(name = "baby")
@SQLDelete(sql = "UPDATE baby SET deleted_at = extract(epoch from now()), "
+ "version = version + 1, updated_at = current_timestamp WHERE id = ? AND version = ?",
check = ResultCheckStyle.COUNT)
@Where(clause = BaseEntity.SOFT_DELETED_CLAUSE)
public class Baby extends BaseEntity {
private String idCard;
}
@SQLDelete
: 我们复写delete
操作的逻辑,所以当jpa里面的例如deleteById
或者我们自定义的deleteByIdCard
的方法时,我们复写的sql语句被执行,而不是默认的delete
语句@Where
: 当我们在查询当前表的时候,会添加指定的过滤条件。上述就是在查询的时候,会额外添加deleted_at = -1
的过滤条件
实现AuditorAware类
上述我们添加了@CreatedDate
和@LastModifiedBy
注解表明这条记录是谁在操作。Jpa是从哪里得到当前操作人的信息呢?那就是AuditorAware
了,我们通过实现AuditorAware
类,比如实现中从token
里面解析得到当前用户的信息username
。下面只是简单的实现以了解原理就行
public class AuditAwareImpl implements AuditorAware<String> {
@Override
public @Nonnull Optional<String> getCurrentAuditor() {
return Optional.of("huhx");
}
}
开启Spirng对Audit的开关
@Configuration
@EnableJpaAuditing
public class JpaConfig {
@Bean
public AuditAwareImpl auditorProvider() {
return new AuditAwareImpl();
}
}
原理
@CreatedBy和@LastModifiedBy的原理
@CreatedBy, @LastModifiedBy, @CreatedDate, @LastModifiedDate这些注解生效的原因在于Spring内置的实现AuditingEntityListener
。AuditingEntityListener
里面定义了Entity在实际创建和更新前的回调函数。
@PrePersist
public void touchForCreate(Object target) {
if (handler != null) {
AuditingHandler object = handler.getObject();
if (object != null) {
object.markCreated(target);
}
}
}
@PreUpdate
public void touchForUpdate(Object target) {
if (handler != null) {
AuditingHandler object = handler.getObject();
if (object != null) {
object.markModified(target);
}
}
}
我们以创建为例,来深入看下touchForCreate
内部的源码。我们直接跟进markCreated
方法。
public <T> T markCreated(T source) {
return markCreated(getAuditor(), source);
}
// 这个方法就是得到审计数据中的CreateBy或者是LastModifiedBy,返回的值是:uditorAware实现类的getCurrentAuditor方法
Auditor<?> getAuditor() {
return auditorAware.map(AuditorAware::getCurrentAuditor).map(Auditor::ofOptional)
.orElse(Auditor.none());
}
<T> T markCreated(Auditor<?> auditor, T source) {
return touch(auditor, source, true);
}
上述得到audit的值,接着作为参数进入到了touch
函数。
private <T> T touch(Auditor<?> auditor, T target, boolean isNew) {
Optional<AuditableBeanWrapper<T>> wrapper = factory.getBeanWrapperFor(target);
return wrapper.map(it -> {
touchAuditor(auditor, it, isNew);
Optional<TemporalAccessor> now = dateTimeForNow ? touchDate(it, isNew) : Optional.empty();
....
return it.getBean();
}).orElse(target);
}
在第四行,默认情况下setCreatedBy
和setLastModifiedBy
就是上述audit
的值。
private void touchAuditor(Auditor<?> auditor, AuditableBeanWrapper<?> wrapper, boolean isNew) {
if(!auditor.isPresent()) return;
if (isNew) {
wrapper.setCreatedBy(auditor.getValue());
}
if (!isNew || modifyOnCreation) {
wrapper.setLastModifiedBy(auditor.getValue());
}
}
接下来,我们看下第五行touchDate
的代码,关于审计时间的设置。
private Optional<TemporalAccessor> touchDate(AuditableBeanWrapper<?> wrapper, boolean isNew) {
Optional<TemporalAccessor> now = dateTimeProvider.getNow();
now.filter(__ -> isNew).ifPresent(wrapper::setCreatedDate);
now.filter(__ -> !isNew || modifyOnCreation).ifPresent(wrapper::setLastModifiedDate);
return now;
}