java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringData JPA审计

SpringData JPA审计功能(@CreatedDate与@LastModifiedDate)实现

作者:程序媛学姐

Spring Data JPA的审计功能提供了一种强大而灵活的机制,用于自动跟踪实体的创建和修改信息,通过使用@CreatedDate和@LastModifiedDate注解,开发者可以轻松地实现时间审计,感兴趣的可以了解一下

引言

在企业级应用开发中,数据审计是一项至关重要的功能。所谓数据审计,是指对数据的创建、修改等操作进行跟踪记录,以便于后续的数据分析、问题追踪和安全审核。Spring Data JPA提供了强大的审计功能,通过简单的注解配置,即可实现对实体创建时间、最后修改时间、创建人和修改人的自动记录。本文将深入探讨Spring Data JPA的审计功能,重点介绍@CreatedDate与@LastModifiedDate注解的实现原理及使用方法,帮助开发者构建健壮的数据审计系统。

一、Spring Data JPA审计功能概述

Spring Data JPA的审计功能是通过实体生命周期事件和AOP切面实现的。它可以在实体被持久化和更新时,自动填充审计字段,从而避免了手动设置这些值的繁琐工作。

1.1 核心审计注解

Spring Data JPA提供了四个核心的审计注解:

这些注解都位于org.springframework.data.annotation包中,是Spring Data通用的审计注解,不仅限于JPA使用。

1.2 审计功能的工作原理

Spring Data JPA的审计功能主要通过以下几个组件协同工作:

当启用审计功能后,每当实体被创建或更新,AuditingEntityListener会捕获相应的事件,并调用AuditingHandler对标记了审计注解的字段进行填充。

二、基础配置

要使用Spring Data JPA的审计功能,首先需要进行必要的配置。

2.1 启用JPA审计功能

在Spring Boot应用中,通过@EnableJpaAuditing注解启用JPA审计功能:

package com.example.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

/**
 * JPA审计功能配置类
 */
@Configuration
@EnableJpaAuditing  // 启用JPA审计功能
public class JpaAuditingConfig {
    // 可以在这里配置审计相关的Bean
}

2.2 创建审计基类

通常,我们会创建一个包含审计字段的基类,让需要审计的实体继承这个基类:

package com.example.entity;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

/**
 * 包含审计字段的基础实体类
 */
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)  // 注册实体监听器
public abstract class AuditableEntity {
    
    @CreatedDate
    @Column(name = "created_date", updatable = false)
    private LocalDateTime createdDate;
    
    @LastModifiedDate
    @Column(name = "last_modified_date")
    private LocalDateTime lastModifiedDate;
    
    // Getter和Setter方法
    public LocalDateTime getCreatedDate() {
        return createdDate;
    }
    
    public void setCreatedDate(LocalDateTime createdDate) {
        this.createdDate = createdDate;
    }
    
    public LocalDateTime getLastModifiedDate() {
        return lastModifiedDate;
    }
    
    public void setLastModifiedDate(LocalDateTime lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }
}

在上面的代码中:

三、实现时间审计

时间审计是最基本的审计功能,涉及到@CreatedDate@LastModifiedDate注解的使用。

3.1 使用审计基类

创建业务实体类并继承审计基类:

package com.example.entity;

import javax.persistence.*;
import java.math.BigDecimal;

/**
 * 产品实体类
 */
@Entity
@Table(name = "tb_product")
public class Product extends AuditableEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "name", nullable = false)
    private String name;
    
    @Column(name = "description")
    private String description;
    
    @Column(name = "price", precision = 10, scale = 2)
    private BigDecimal price;
    
    @Column(name = "stock")
    private Integer stock;
    
    // Getter和Setter方法省略
}

继承AuditableEntity后,Product实体将自动拥有createdDatelastModifiedDate字段,这些字段会在实体创建和更新时自动填充。

3.2 不使用基类的审计实现

如果因为某些原因不想使用继承,也可以直接在实体类中使用审计注解:

package com.example.entity;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.time.LocalDateTime;

/**
 * 订单实体类
 */
@Entity
@Table(name = "tb_order")
@EntityListeners(AuditingEntityListener.class)  // 直接在实体类上注册监听器
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "order_number", nullable = false, unique = true)
    private String orderNumber;
    
    @Column(name = "customer_id")
    private Long customerId;
    
    @Column(name = "total_amount", precision = 10, scale = 2)
    private BigDecimal totalAmount;
    
    @Column(name = "status")
    private String status;
    
    @CreatedDate
    @Column(name = "created_date", updatable = false)
    private LocalDateTime createdDate;
    
    @LastModifiedDate
    @Column(name = "last_modified_date")
    private LocalDateTime lastModifiedDate;
    
    // Getter和Setter方法省略
}

3.3 自定义日期时间提供者

如果需要自定义日期时间的提供方式,可以实现DateTimeProvider接口:

package com.example.audit;

import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAccessor;
import java.util.Optional;

/**
 * 自定义日期时间提供者
 */
@Component
public class CustomDateTimeProvider implements DateTimeProvider {
    
    @Override
    public Optional<TemporalAccessor> getNow() {
        // 使用特定时区的当前时间
        return Optional.of(LocalDateTime.now(ZoneId.of("Asia/Shanghai")));
    }
}

然后在配置类中注册这个提供者:

package com.example.config;

import com.example.audit.CustomDateTimeProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

/**
 * JPA审计功能配置类
 */
@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider")  // 指定日期时间提供者
public class JpaAuditingConfig {
    
    @Bean
    public DateTimeProvider dateTimeProvider() {
        return new CustomDateTimeProvider();
    }
}

四、实现用户审计

除了时间审计,还可以实现用户审计,即记录创建和修改实体的用户。

4.1 扩展审计基类

扩展审计基类,添加用户审计字段:

package com.example.entity;

import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

/**
 * 包含完整审计字段的基础实体类
 */
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class FullAuditableEntity {
    
    @CreatedDate
    @Column(name = "created_date", updatable = false)
    private LocalDateTime createdDate;
    
    @LastModifiedDate
    @Column(name = "last_modified_date")
    private LocalDateTime lastModifiedDate;
    
    @CreatedBy
    @Column(name = "created_by", updatable = false)
    private String createdBy;
    
    @LastModifiedBy
    @Column(name = "last_modified_by")
    private String lastModifiedBy;
    
    // Getter和Setter方法省略
}

4.2 实现AuditorAware接口

要使用@CreatedBy@LastModifiedBy注解,需要实现AuditorAware接口,提供当前用户信息:

package com.example.audit;

import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.util.Optional;

/**
 * 当前用户提供者
 */
@Component
public class SpringSecurityAuditorAware implements AuditorAware<String> {
    
    @Override
    public Optional<String> getCurrentAuditor() {
        // 从Spring Security上下文中获取当前用户
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        if (authentication == null || !authentication.isAuthenticated()) {
            return Optional.of("anonymousUser");
        }
        
        return Optional.of(authentication.getName());
    }
}

然后在配置类中注册这个提供者:

package com.example.config;

import com.example.audit.CustomDateTimeProvider;
import com.example.audit.SpringSecurityAuditorAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

/**
 * JPA审计功能配置类
 */
@Configuration
@EnableJpaAuditing(
    dateTimeProviderRef = "dateTimeProvider",
    auditorAwareRef = "auditorAware"  // 指定用户提供者
)
public class JpaAuditingConfig {
    
    @Bean
    public DateTimeProvider dateTimeProvider() {
        return new CustomDateTimeProvider();
    }
    
    @Bean
    public AuditorAware<String> auditorAware() {
        return new SpringSecurityAuditorAware();
    }
}

4.3 使用完整审计实体

创建业务实体并继承完整审计基类:

package com.example.entity;

import javax.persistence.*;
import java.math.BigDecimal;

/**
 * 订单实体类
 */
@Entity
@Table(name = "tb_order")
public class Order extends FullAuditableEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "order_number", nullable = false, unique = true)
    private String orderNumber;
    
    @Column(name = "customer_id")
    private Long customerId;
    
    @Column(name = "total_amount", precision = 10, scale = 2)
    private BigDecimal totalAmount;
    
    @Column(name = "status")
    private String status;
    
    // Getter和Setter方法省略
}

五、实际应用场景

Spring Data JPA的审计功能在实际开发中有广泛的应用场景。

5.1 数据版本控制

结合版本控制字段,实现乐观锁和数据版本追踪:

package com.example.entity;

import javax.persistence.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;

/**
 * 文档实体类
 */
@Entity
@Table(name = "tb_document")
@EntityListeners(AuditingEntityListener.class)
public class Document {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "title")
    private String title;
    
    @Column(name = "content", columnDefinition = "TEXT")
    private String content;
    
    @Version  // 版本控制字段
    @Column(name = "version")
    private Long version;
    
    @CreatedDate
    @Column(name = "created_date", updatable = false)
    private LocalDateTime createdDate;
    
    @LastModifiedDate
    @Column(name = "last_modified_date")
    private LocalDateTime lastModifiedDate;
    
    // Getter和Setter方法省略
}

5.2 审计日志记录

利用实体监听器,实现更详细的审计日志记录:

package com.example.listener;

import com.example.entity.AuditLog;
import com.example.entity.Product;
import com.example.repository.AuditLogRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import java.time.LocalDateTime;

/**
 * 产品审计监听器
 */
@Component
public class ProductAuditListener {
    
    @Autowired
    private AuditLogRepository auditLogRepository;
    
    private static final ThreadLocal<Product> originalState = new ThreadLocal<>();
    
    @PrePersist
    public void prePersist(Product product) {
        // 新建产品前的操作
    }
    
    @PostPersist
    public void postPersist(Product product) {
        // 记录产品创建日志
        AuditLog log = new AuditLog();
        log.setEntityType("Product");
        log.setEntityId(product.getId().toString());
        log.setAction("CREATE");
        log.setTimestamp(LocalDateTime.now());
        log.setDetails("Created product: " + product.getName());
        
        auditLogRepository.save(log);
    }
    
    @PreUpdate
    public void preUpdate(Product product) {
        // 保存产品原始状态
        Product original = new Product();
        // 复制product的属性到original
        originalState.set(original);
    }
    
    @PostUpdate
    public void postUpdate(Product product) {
        // 获取原始状态
        Product original = originalState.get();
        
        // 记录产品更新日志
        AuditLog log = new AuditLog();
        log.setEntityType("Product");
        log.setEntityId(product.getId().toString());
        log.setAction("UPDATE");
        log.setTimestamp(LocalDateTime.now());
        
        // 构建变更信息
        StringBuilder changes = new StringBuilder();
        if (!product.getName().equals(original.getName())) {
            changes.append("Name changed from '")
                  .append(original.getName())
                  .append("' to '")
                  .append(product.getName())
                  .append("'. ");
        }
        // 其他字段变更检查...
        
        log.setDetails(changes.toString());
        auditLogRepository.save(log);
        
        // 清理ThreadLocal
        originalState.remove();
    }
}

要启用这个监听器,需要在Product实体上注册:

@Entity
@Table(name = "tb_product")
@EntityListeners({AuditingEntityListener.class, ProductAuditListener.class})
public class Product extends AuditableEntity {
    // 实体内容
}

5.3 多租户审计

在多租户系统中,结合审计功能实现租户数据隔离:

package com.example.entity;

import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

/**
 * 多租户审计基类
 */
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class TenantAuditableEntity {
    
    @Column(name = "tenant_id", nullable = false, updatable = false)
    private String tenantId;
    
    @CreatedDate
    @Column(name = "created_date", updatable = false)
    private LocalDateTime createdDate;
    
    @LastModifiedDate
    @Column(name = "last_modified_date")
    private LocalDateTime lastModifiedDate;
    
    @CreatedBy
    @Column(name = "created_by", updatable = false)
    private String createdBy;
    
    @LastModifiedBy
    @Column(name = "last_modified_by")
    private String lastModifiedBy;
    
    // Getter和Setter方法省略
}

使用多租户审计实体:

package com.example.entity;

import javax.persistence.*;

/**
 * 客户实体类
 */
@Entity
@Table(name = "tb_customer")
public class Customer extends TenantAuditableEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "name")
    private String name;
    
    @Column(name = "email")
    private String email;
    
    @Column(name = "phone")
    private String phone;
    
    // Getter和Setter方法省略
}

六、高级技巧

Spring Data JPA审计功能还有一些高级用法,可以满足更复杂的审计需求。

6.1 条件审计

有时候我们只希望在特定条件下进行审计。可以通过自定义实体监听器实现:

package com.example.listener;

import com.example.entity.User;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;

import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import java.lang.reflect.Field;
import java.time.LocalDateTime;

/**
 * 条件审计监听器
 */
public class ConditionalAuditListener {
    
    @PrePersist
    public void touchForCreate(Object target) {
        // 只对激活状态的用户进行审计
        if (target instanceof User) {
            User user = (User) target;
            if (user.isActive()) {
                setCreatedDate(user);
            }
        }
    }
    
    @PreUpdate
    public void touchForUpdate(Object target) {
        // 只对激活状态的用户进行审计
        if (target instanceof User) {
            User user = (User) target;
            if (user.isActive()) {
                setLastModifiedDate(user);
            }
        }
    }
    
    private void setCreatedDate(Object target) {
        setFieldValue(target, CreatedDate.class, LocalDateTime.now());
    }
    
    private void setLastModifiedDate(Object target) {
        setFieldValue(target, LastModifiedDate.class, LocalDateTime.now());
    }
    
    private void setFieldValue(Object target, Class annotation, Object value) {
        try {
            for (Field field : target.getClass().getDeclaredFields()) {
                if (field.isAnnotationPresent(annotation)) {
                    field.setAccessible(true);
                    field.set(target, value);
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Failed to set auditing field", e);
        }
    }
}

6.2 自定义审计注解

可以创建自定义审计注解,实现更灵活的审计逻辑:

package com.example.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义审计注解:记录字段的历史值
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TrackChanges {
    String value() default "";
}

然后实现对应的监听器处理这个注解:

package com.example.listener;

import com.example.annotation.TrackChanges;
import com.example.entity.FieldChangeLog;
import com.example.repository.FieldChangeLogRepository;
import org.springframework.beans.factory.annotation.Autowired;

import javax.persistence.PreUpdate;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Objects;

/**
 * 字段变更跟踪监听器
 */
public class FieldChangeTrackingListener {
    
    @Autowired
    private FieldChangeLogRepository changeLogRepository;
    
    @PreUpdate
    public void preUpdate(Object entity) {
        try {
            // 获取实体ID
            Long entityId = getEntityId(entity);
            String entityType = entity.getClass().getSimpleName();
            
            // 查找标记了@TrackChanges的字段
            for (Field field : entity.getClass().getDeclaredFields()) {
                TrackChanges annotation = field.getAnnotation(TrackChanges.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    
                    // 获取字段新值
                    Object newValue = field.get(entity);
                    
                    // 从数据库获取原始实体和字段旧值
                    Object originalEntity = loadOriginalEntity(entityId, entity.getClass());
                    field.setAccessible(true);
                    Object oldValue = field.get(originalEntity);
                    
                    // 如果值发生变化,记录日志
                    if (!Objects.equals(oldValue, newValue)) {
                        FieldChangeLog changeLog = new FieldChangeLog();
                        changeLog.setEntityType(entityType);
                        changeLog.setEntityId(entityId);
                        changeLog.setFieldName(field.getName());
                        changeLog.setOldValue(oldValue != null ? oldValue.toString() : null);
                        changeLog.setNewValue(newValue != null ? newValue.toString() : null);
                        changeLog.setChangedAt(LocalDateTime.now());
                        
                        changeLogRepository.save(changeLog);
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Failed to track field changes", e);
        }
    }
    
    private Long getEntityId(Object entity) throws Exception {
        // 获取实体ID的逻辑
        // ...
        return null;
    }
    
    private Object loadOriginalEntity(Long entityId, Class<?> entityClass) {
        // 从数据库加载原始实体的逻辑
        // ...
        return null;
    }
}

总结

Spring Data JPA的审计功能提供了一种强大而灵活的机制,用于自动跟踪实体的创建和修改信息。通过使用@CreatedDate和@LastModifiedDate注解,开发者可以轻松地实现时间审计;结合@CreatedBy和@LastModifiedBy注解以及AuditorAware接口,还可以实现用户审计。这些功能大大简化了审计系统的开发工作,使开发者能够专注于业务逻辑的实现。

在实际应用中,审计功能可以与版本控制、详细日志记录和多租户系统等场景结合,满足不同的业务需求。通过自定义实体监听器和审计注解,还可以实现更加复杂和灵活的审计逻辑。总之,Spring Data JPA的审计功能是构建健壮企业级应用的重要组成部分,对于提高系统的可追溯性和安全性具有重要意义。

到此这篇关于SpringData JPA审计功能(@CreatedDate与@LastModifiedDate)实现的文章就介绍到这了,更多相关SpringData JPA审计内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文