欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringData JPA審計功能(@CreatedDate與@LastModifiedDate)實現(xiàn)

 更新時間:2025年04月13日 10:44:43   作者:程序媛學(xué)姐  
Spring Data JPA的審計功能提供了一種強大而靈活的機制,用于自動跟蹤實體的創(chuàng)建和修改信息,通過使用@CreatedDate和@LastModifiedDate注解,開發(fā)者可以輕松地實現(xiàn)時間審計,感興趣的可以了解一下

引言

在企業(yè)級應(yīng)用開發(fā)中,數(shù)據(jù)審計是一項至關(guān)重要的功能。所謂數(shù)據(jù)審計,是指對數(shù)據(jù)的創(chuàng)建、修改等操作進行跟蹤記錄,以便于后續(xù)的數(shù)據(jù)分析、問題追蹤和安全審核。Spring Data JPA提供了強大的審計功能,通過簡單的注解配置,即可實現(xiàn)對實體創(chuàng)建時間、最后修改時間、創(chuàng)建人和修改人的自動記錄。本文將深入探討Spring Data JPA的審計功能,重點介紹@CreatedDate與@LastModifiedDate注解的實現(xiàn)原理及使用方法,幫助開發(fā)者構(gòu)建健壯的數(shù)據(jù)審計系統(tǒng)。

一、Spring Data JPA審計功能概述

Spring Data JPA的審計功能是通過實體生命周期事件和AOP切面實現(xiàn)的。它可以在實體被持久化和更新時,自動填充審計字段,從而避免了手動設(shè)置這些值的繁瑣工作。

1.1 核心審計注解

Spring Data JPA提供了四個核心的審計注解:

  • @CreatedDate:標(biāo)記實體創(chuàng)建時間字段
  • @LastModifiedDate:標(biāo)記實體最后修改時間字段
  • @CreatedBy:標(biāo)記實體創(chuàng)建者字段
  • @LastModifiedBy:標(biāo)記實體最后修改者字段

這些注解都位于org.springframework.data.annotation包中,是Spring Data通用的審計注解,不僅限于JPA使用。

1.2 審計功能的工作原理

Spring Data JPA的審計功能主要通過以下幾個組件協(xié)同工作:

  • AuditingEntityListener:JPA實體監(jiān)聽器,負(fù)責(zé)捕獲實體的生命周期事件
  • AuditingHandler:處理審計信息的填充邏輯
  • DateTimeProvider:提供當(dāng)前時間的接口
  • AuditorAware:提供當(dāng)前用戶信息的接口

當(dāng)啟用審計功能后,每當(dāng)實體被創(chuàng)建或更新,AuditingEntityListener會捕獲相應(yīng)的事件,并調(diào)用AuditingHandler對標(biāo)記了審計注解的字段進行填充。

二、基礎(chǔ)配置

要使用Spring Data JPA的審計功能,首先需要進行必要的配置。

2.1 啟用JPA審計功能

在Spring Boot應(yīng)用中,通過@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 {
    // 可以在這里配置審計相關(guān)的Bean
}

2.2 創(chuàng)建審計基類

通常,我們會創(chuàng)建一個包含審計字段的基類,讓需要審計的實體繼承這個基類:

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;

/**
 * 包含審計字段的基礎(chǔ)實體類
 */
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)  // 注冊實體監(jiān)聽器
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;
    }
}

在上面的代碼中:

  • @MappedSuperclass注解表明這是一個映射超類,其字段將被映射到子類的表中
  • @EntityListeners(AuditingEntityListener.class)注冊了JPA實體監(jiān)聽器,用于捕獲實體的生命周期事件
  • @CreatedDate標(biāo)記了實體創(chuàng)建時間字段
  • @LastModifiedDate標(biāo)記了實體最后修改時間字段
  • updatable = false確保createdDate字段在更新時不會被修改

三、實現(xiàn)時間審計

時間審計是最基本的審計功能,涉及到@CreatedDate@LastModifiedDate注解的使用。

3.1 使用審計基類

創(chuàng)建業(yè)務(wù)實體類并繼承審計基類:

package com.example.entity;

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

/**
 * 產(chǎn)品實體類
 */
@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字段,這些字段會在實體創(chuàng)建和更新時自動填充。

3.2 不使用基類的審計實現(xiàn)

如果因為某些原因不想使用繼承,也可以直接在實體類中使用審計注解:

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)  // 直接在實體類上注冊監(jiān)聽器
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 自定義日期時間提供者

如果需要自定義日期時間的提供方式,可以實現(xiàn)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() {
        // 使用特定時區(qū)的當(dāng)前時間
        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();
    }
}

四、實現(xiàn)用戶審計

除了時間審計,還可以實現(xiàn)用戶審計,即記錄創(chuàng)建和修改實體的用戶。

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;

/**
 * 包含完整審計字段的基礎(chǔ)實體類
 */
@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 實現(xiàn)AuditorAware接口

要使用@CreatedBy@LastModifiedBy注解,需要實現(xiàn)AuditorAware接口,提供當(dāng)前用戶信息:

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;

/**
 * 當(dāng)前用戶提供者
 */
@Component
public class SpringSecurityAuditorAware implements AuditorAware<String> {
    
    @Override
    public Optional<String> getCurrentAuditor() {
        // 從Spring Security上下文中獲取當(dāng)前用戶
        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 使用完整審計實體

創(chuàng)建業(yè)務(wù)實體并繼承完整審計基類:

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方法省略
}

五、實際應(yīng)用場景

Spring Data JPA的審計功能在實際開發(fā)中有廣泛的應(yīng)用場景。

5.1 數(shù)據(jù)版本控制

結(jié)合版本控制字段,實現(xiàn)樂觀鎖和數(shù)據(jù)版本追蹤:

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 審計日志記錄

利用實體監(jiān)聽器,實現(xiàn)更詳細的審計日志記錄:

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;

/**
 * 產(chǎn)品審計監(jiān)聽器
 */
@Component
public class ProductAuditListener {
    
    @Autowired
    private AuditLogRepository auditLogRepository;
    
    private static final ThreadLocal<Product> originalState = new ThreadLocal<>();
    
    @PrePersist
    public void prePersist(Product product) {
        // 新建產(chǎn)品前的操作
    }
    
    @PostPersist
    public void postPersist(Product product) {
        // 記錄產(chǎn)品創(chuàng)建日志
        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) {
        // 保存產(chǎn)品原始狀態(tài)
        Product original = new Product();
        // 復(fù)制product的屬性到original
        originalState.set(original);
    }
    
    @PostUpdate
    public void postUpdate(Product product) {
        // 獲取原始狀態(tài)
        Product original = originalState.get();
        
        // 記錄產(chǎn)品更新日志
        AuditLog log = new AuditLog();
        log.setEntityType("Product");
        log.setEntityId(product.getId().toString());
        log.setAction("UPDATE");
        log.setTimestamp(LocalDateTime.now());
        
        // 構(gòu)建變更信息
        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();
    }
}

要啟用這個監(jiān)聽器,需要在Product實體上注冊:

@Entity
@Table(name = "tb_product")
@EntityListeners({AuditingEntityListener.class, ProductAuditListener.class})
public class Product extends AuditableEntity {
    // 實體內(nèi)容
}

5.3 多租戶審計

在多租戶系統(tǒng)中,結(jié)合審計功能實現(xiàn)租戶數(shù)據(jù)隔離:

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審計功能還有一些高級用法,可以滿足更復(fù)雜的審計需求。

6.1 條件審計

有時候我們只希望在特定條件下進行審計??梢酝ㄟ^自定義實體監(jiān)聽器實現(xiàn):

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;

/**
 * 條件審計監(jiān)聽器
 */
public class ConditionalAuditListener {
    
    @PrePersist
    public void touchForCreate(Object target) {
        // 只對激活狀態(tài)的用戶進行審計
        if (target instanceof User) {
            User user = (User) target;
            if (user.isActive()) {
                setCreatedDate(user);
            }
        }
    }
    
    @PreUpdate
    public void touchForUpdate(Object target) {
        // 只對激活狀態(tài)的用戶進行審計
        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 自定義審計注解

可以創(chuàng)建自定義審計注解,實現(xiàn)更靈活的審計邏輯:

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 "";
}

然后實現(xiàn)對應(yīng)的監(jiān)聽器處理這個注解:

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;

/**
 * 字段變更跟蹤監(jiān)聽器
 */
public class FieldChangeTrackingListener {
    
    @Autowired
    private FieldChangeLogRepository changeLogRepository;
    
    @PreUpdate
    public void preUpdate(Object entity) {
        try {
            // 獲取實體ID
            Long entityId = getEntityId(entity);
            String entityType = entity.getClass().getSimpleName();
            
            // 查找標(biāo)記了@TrackChanges的字段
            for (Field field : entity.getClass().getDeclaredFields()) {
                TrackChanges annotation = field.getAnnotation(TrackChanges.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    
                    // 獲取字段新值
                    Object newValue = field.get(entity);
                    
                    // 從數(shù)據(jù)庫獲取原始實體和字段舊值
                    Object originalEntity = loadOriginalEntity(entityId, entity.getClass());
                    field.setAccessible(true);
                    Object oldValue = field.get(originalEntity);
                    
                    // 如果值發(fā)生變化,記錄日志
                    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) {
        // 從數(shù)據(jù)庫加載原始實體的邏輯
        // ...
        return null;
    }
}

總結(jié)

Spring Data JPA的審計功能提供了一種強大而靈活的機制,用于自動跟蹤實體的創(chuàng)建和修改信息。通過使用@CreatedDate和@LastModifiedDate注解,開發(fā)者可以輕松地實現(xiàn)時間審計;結(jié)合@CreatedBy和@LastModifiedBy注解以及AuditorAware接口,還可以實現(xiàn)用戶審計。這些功能大大簡化了審計系統(tǒng)的開發(fā)工作,使開發(fā)者能夠?qū)W⒂跇I(yè)務(wù)邏輯的實現(xiàn)。

在實際應(yīng)用中,審計功能可以與版本控制、詳細日志記錄和多租戶系統(tǒng)等場景結(jié)合,滿足不同的業(yè)務(wù)需求。通過自定義實體監(jiān)聽器和審計注解,還可以實現(xiàn)更加復(fù)雜和靈活的審計邏輯??傊?,Spring Data JPA的審計功能是構(gòu)建健壯企業(yè)級應(yīng)用的重要組成部分,對于提高系統(tǒng)的可追溯性和安全性具有重要意義。

到此這篇關(guān)于SpringData JPA審計功能(@CreatedDate與@LastModifiedDate)實現(xiàn)的文章就介紹到這了,更多相關(guān)SpringData JPA審計內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論