SpringData JPA審計(jì)功能(@CreatedDate與@LastModifiedDate)實(shí)現(xiàn)
引言
在企業(yè)級(jí)應(yīng)用開(kāi)發(fā)中,數(shù)據(jù)審計(jì)是一項(xiàng)至關(guān)重要的功能。所謂數(shù)據(jù)審計(jì),是指對(duì)數(shù)據(jù)的創(chuàng)建、修改等操作進(jìn)行跟蹤記錄,以便于后續(xù)的數(shù)據(jù)分析、問(wèn)題追蹤和安全審核。Spring Data JPA提供了強(qiáng)大的審計(jì)功能,通過(guò)簡(jiǎn)單的注解配置,即可實(shí)現(xiàn)對(duì)實(shí)體創(chuàng)建時(shí)間、最后修改時(shí)間、創(chuàng)建人和修改人的自動(dòng)記錄。本文將深入探討Spring Data JPA的審計(jì)功能,重點(diǎn)介紹@CreatedDate與@LastModifiedDate注解的實(shí)現(xiàn)原理及使用方法,幫助開(kāi)發(fā)者構(gòu)建健壯的數(shù)據(jù)審計(jì)系統(tǒng)。
一、Spring Data JPA審計(jì)功能概述
Spring Data JPA的審計(jì)功能是通過(guò)實(shí)體生命周期事件和AOP切面實(shí)現(xiàn)的。它可以在實(shí)體被持久化和更新時(shí),自動(dòng)填充審計(jì)字段,從而避免了手動(dòng)設(shè)置這些值的繁瑣工作。
1.1 核心審計(jì)注解
Spring Data JPA提供了四個(gè)核心的審計(jì)注解:
@CreatedDate:標(biāo)記實(shí)體創(chuàng)建時(shí)間字段@LastModifiedDate:標(biāo)記實(shí)體最后修改時(shí)間字段@CreatedBy:標(biāo)記實(shí)體創(chuàng)建者字段@LastModifiedBy:標(biāo)記實(shí)體最后修改者字段
這些注解都位于org.springframework.data.annotation包中,是Spring Data通用的審計(jì)注解,不僅限于JPA使用。
1.2 審計(jì)功能的工作原理
Spring Data JPA的審計(jì)功能主要通過(guò)以下幾個(gè)組件協(xié)同工作:
AuditingEntityListener:JPA實(shí)體監(jiān)聽(tīng)器,負(fù)責(zé)捕獲實(shí)體的生命周期事件AuditingHandler:處理審計(jì)信息的填充邏輯DateTimeProvider:提供當(dāng)前時(shí)間的接口AuditorAware:提供當(dāng)前用戶信息的接口
當(dāng)啟用審計(jì)功能后,每當(dāng)實(shí)體被創(chuàng)建或更新,AuditingEntityListener會(huì)捕獲相應(yīng)的事件,并調(diào)用AuditingHandler對(duì)標(biāo)記了審計(jì)注解的字段進(jìn)行填充。
二、基礎(chǔ)配置
要使用Spring Data JPA的審計(jì)功能,首先需要進(jìn)行必要的配置。
2.1 啟用JPA審計(jì)功能
在Spring Boot應(yīng)用中,通過(guò)@EnableJpaAuditing注解啟用JPA審計(jì)功能:
package com.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/**
* JPA審計(jì)功能配置類
*/
@Configuration
@EnableJpaAuditing // 啟用JPA審計(jì)功能
public class JpaAuditingConfig {
// 可以在這里配置審計(jì)相關(guān)的Bean
}
2.2 創(chuàng)建審計(jì)基類
通常,我們會(huì)創(chuàng)建一個(gè)包含審計(jì)字段的基類,讓需要審計(jì)的實(shí)體繼承這個(gè)基類:
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;
/**
* 包含審計(jì)字段的基礎(chǔ)實(shí)體類
*/
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class) // 注冊(cè)實(shí)體監(jiān)聽(tīng)器
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注解表明這是一個(gè)映射超類,其字段將被映射到子類的表中@EntityListeners(AuditingEntityListener.class)注冊(cè)了JPA實(shí)體監(jiān)聽(tīng)器,用于捕獲實(shí)體的生命周期事件@CreatedDate標(biāo)記了實(shí)體創(chuàng)建時(shí)間字段@LastModifiedDate標(biāo)記了實(shí)體最后修改時(shí)間字段updatable = false確保createdDate字段在更新時(shí)不會(huì)被修改
三、實(shí)現(xiàn)時(shí)間審計(jì)
時(shí)間審計(jì)是最基本的審計(jì)功能,涉及到@CreatedDate和@LastModifiedDate注解的使用。
3.1 使用審計(jì)基類
創(chuàng)建業(yè)務(wù)實(shí)體類并繼承審計(jì)基類:
package com.example.entity;
import javax.persistence.*;
import java.math.BigDecimal;
/**
* 產(chǎn)品實(shí)體類
*/
@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實(shí)體將自動(dòng)擁有createdDate和lastModifiedDate字段,這些字段會(huì)在實(shí)體創(chuàng)建和更新時(shí)自動(dòng)填充。
3.2 不使用基類的審計(jì)實(shí)現(xiàn)
如果因?yàn)槟承┰虿幌胧褂美^承,也可以直接在實(shí)體類中使用審計(jì)注解:
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;
/**
* 訂單實(shí)體類
*/
@Entity
@Table(name = "tb_order")
@EntityListeners(AuditingEntityListener.class) // 直接在實(shí)體類上注冊(cè)監(jiān)聽(tīng)器
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 自定義日期時(shí)間提供者
如果需要自定義日期時(shí)間的提供方式,可以實(shí)現(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;
/**
* 自定義日期時(shí)間提供者
*/
@Component
public class CustomDateTimeProvider implements DateTimeProvider {
@Override
public Optional<TemporalAccessor> getNow() {
// 使用特定時(shí)區(qū)的當(dāng)前時(shí)間
return Optional.of(LocalDateTime.now(ZoneId.of("Asia/Shanghai")));
}
}
然后在配置類中注冊(cè)這個(gè)提供者:
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審計(jì)功能配置類
*/
@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider") // 指定日期時(shí)間提供者
public class JpaAuditingConfig {
@Bean
public DateTimeProvider dateTimeProvider() {
return new CustomDateTimeProvider();
}
}
四、實(shí)現(xiàn)用戶審計(jì)
除了時(shí)間審計(jì),還可以實(shí)現(xiàn)用戶審計(jì),即記錄創(chuàng)建和修改實(shí)體的用戶。
4.1 擴(kuò)展審計(jì)基類
擴(kuò)展審計(jì)基類,添加用戶審計(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;
/**
* 包含完整審計(jì)字段的基礎(chǔ)實(shí)體類
*/
@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 實(shí)現(xiàn)AuditorAware接口
要使用@CreatedBy和@LastModifiedBy注解,需要實(shí)現(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());
}
}
然后在配置類中注冊(cè)這個(gè)提供者:
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審計(jì)功能配置類
*/
@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 使用完整審計(jì)實(shí)體
創(chuàng)建業(yè)務(wù)實(shí)體并繼承完整審計(jì)基類:
package com.example.entity;
import javax.persistence.*;
import java.math.BigDecimal;
/**
* 訂單實(shí)體類
*/
@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方法省略
}
五、實(shí)際應(yīng)用場(chǎng)景
Spring Data JPA的審計(jì)功能在實(shí)際開(kāi)發(fā)中有廣泛的應(yīng)用場(chǎng)景。
5.1 數(shù)據(jù)版本控制
結(jié)合版本控制字段,實(shí)現(xiàn)樂(lè)觀鎖和數(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;
/**
* 文檔實(shí)體類
*/
@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 審計(jì)日志記錄
利用實(shí)體監(jiān)聽(tīng)器,實(shí)現(xiàn)更詳細(xì)的審計(jì)日志記錄:
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)品審計(jì)監(jiān)聽(tīng)器
*/
@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();
}
}
要啟用這個(gè)監(jiān)聽(tīng)器,需要在Product實(shí)體上注冊(cè):
@Entity
@Table(name = "tb_product")
@EntityListeners({AuditingEntityListener.class, ProductAuditListener.class})
public class Product extends AuditableEntity {
// 實(shí)體內(nèi)容
}
5.3 多租戶審計(jì)
在多租戶系統(tǒng)中,結(jié)合審計(jì)功能實(shí)現(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;
/**
* 多租戶審計(jì)基類
*/
@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方法省略
}
使用多租戶審計(jì)實(shí)體:
package com.example.entity;
import javax.persistence.*;
/**
* 客戶實(shí)體類
*/
@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方法省略
}
六、高級(jí)技巧
Spring Data JPA審計(jì)功能還有一些高級(jí)用法,可以滿足更復(fù)雜的審計(jì)需求。
6.1 條件審計(jì)
有時(shí)候我們只希望在特定條件下進(jìn)行審計(jì)??梢酝ㄟ^(guò)自定義實(shí)體監(jiān)聽(tīng)器實(shí)現(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;
/**
* 條件審計(jì)監(jiān)聽(tīng)器
*/
public class ConditionalAuditListener {
@PrePersist
public void touchForCreate(Object target) {
// 只對(duì)激活狀態(tài)的用戶進(jìn)行審計(jì)
if (target instanceof User) {
User user = (User) target;
if (user.isActive()) {
setCreatedDate(user);
}
}
}
@PreUpdate
public void touchForUpdate(Object target) {
// 只對(duì)激活狀態(tài)的用戶進(jìn)行審計(jì)
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 自定義審計(jì)注解
可以創(chuàng)建自定義審計(jì)注解,實(shí)現(xiàn)更靈活的審計(jì)邏輯:
package com.example.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定義審計(jì)注解:記錄字段的歷史值
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TrackChanges {
String value() default "";
}
然后實(shí)現(xiàn)對(duì)應(yīng)的監(jiān)聽(tīng)器處理這個(gè)注解:
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)聽(tīng)器
*/
public class FieldChangeTrackingListener {
@Autowired
private FieldChangeLogRepository changeLogRepository;
@PreUpdate
public void preUpdate(Object entity) {
try {
// 獲取實(shí)體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ù)庫(kù)獲取原始實(shí)體和字段舊值
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 {
// 獲取實(shí)體ID的邏輯
// ...
return null;
}
private Object loadOriginalEntity(Long entityId, Class<?> entityClass) {
// 從數(shù)據(jù)庫(kù)加載原始實(shí)體的邏輯
// ...
return null;
}
}
總結(jié)
Spring Data JPA的審計(jì)功能提供了一種強(qiáng)大而靈活的機(jī)制,用于自動(dòng)跟蹤實(shí)體的創(chuàng)建和修改信息。通過(guò)使用@CreatedDate和@LastModifiedDate注解,開(kāi)發(fā)者可以輕松地實(shí)現(xiàn)時(shí)間審計(jì);結(jié)合@CreatedBy和@LastModifiedBy注解以及AuditorAware接口,還可以實(shí)現(xiàn)用戶審計(jì)。這些功能大大簡(jiǎn)化了審計(jì)系統(tǒng)的開(kāi)發(fā)工作,使開(kāi)發(fā)者能夠?qū)W⒂跇I(yè)務(wù)邏輯的實(shí)現(xiàn)。
在實(shí)際應(yīng)用中,審計(jì)功能可以與版本控制、詳細(xì)日志記錄和多租戶系統(tǒng)等場(chǎng)景結(jié)合,滿足不同的業(yè)務(wù)需求。通過(guò)自定義實(shí)體監(jiān)聽(tīng)器和審計(jì)注解,還可以實(shí)現(xiàn)更加復(fù)雜和靈活的審計(jì)邏輯??傊琒pring Data JPA的審計(jì)功能是構(gòu)建健壯企業(yè)級(jí)應(yīng)用的重要組成部分,對(duì)于提高系統(tǒng)的可追溯性和安全性具有重要意義。
到此這篇關(guān)于SpringData JPA審計(jì)功能(@CreatedDate與@LastModifiedDate)實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringData JPA審計(jì)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java編程利用socket多線程訪問(wèn)服務(wù)器文件代碼示例
這篇文章主要介紹了Java編程利用socket多線程訪問(wèn)服務(wù)器文件代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-10-10
從零搭建Spring Boot腳手架整合OSS作為文件服務(wù)器的詳細(xì)教程
這篇文章主要介紹了從零搭建Spring Boot腳手架整合OSS作為文件服務(wù)器的詳細(xì)教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08
nacos配置中心遠(yuǎn)程調(diào)用讀取不到配置文件的解決
這篇文章主要介紹了nacos配置中心遠(yuǎn)程調(diào)用讀取不到配置文件的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。2022-01-01
一文了解Java Log框架徹底搞懂Log4J,Log4J2,LogBack,SLF4J
本文主要介紹了一文了解Java Log框架徹底搞懂Log4J,Log4J2,LogBack,SLF4J,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
Java網(wǎng)絡(luò)編程之URL+URLconnection使用方法示例
這篇文章主要介紹了Java網(wǎng)絡(luò)編程之URL+URLconnection使用方法示例,還是比較不錯(cuò)的,這里分享給大家,供需要的朋友參考。2017-11-11
基于Java HttpClient和Htmlparser實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲代碼
這篇文章主要介紹了基于Java HttpClient和Htmlparser實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲代碼的相關(guān)資料,需要的朋友可以參考下2015-12-12
SpringCloud Zuul和Gateway的實(shí)例代碼(搭建方式)
本文主要介紹了SpringCloudZuul和SpringCloudGateway的簡(jiǎn)單示例,SpringCloudGateway是推薦使用的API網(wǎng)關(guān)解決方案,基于SpringFramework5和ProjectReactor構(gòu)建,具有更高的性能和吞吐量2025-02-02
使用JSCH框架通過(guò)跳轉(zhuǎn)機(jī)訪問(wèn)其他節(jié)點(diǎn)的方法
下面小編就為大家分享一篇使用JSCH框架通過(guò)跳轉(zhuǎn)機(jī)訪問(wèn)其他節(jié)點(diǎn)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12

