书接上文
本文解决前面两个问题中的第二个问题,我们将为实体加上创建者和修改者的信息
首先创建一个返回授权用户信息的组件
Spring Data JPA使用AuditorAware<T>
接口获取用户信息,AuditorAware
接口的泛型参数T描述了实体类中审计人的类型
现在开始创建一个返回用户信息的类:
UsernameAuditorAware
类实现AuditorAware
接口,我们想存储String
类型的用户名,所以参数T设置为String
getCurrentAuditor()
方法: SecurityContext
获取Authentication
对象null
或者未经认证,返回null
UsernameAuditorAware
类源码如下:
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
public class UsernameAuditorAware implements AuditorAware<String> {
@Override
public String getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return null;
}
return ((User) authentication.getPrincipal()).getUsername();
}
}
下面我们将UsernameAuditorAware
配置成一个bean,修改PersistenceContext
类,步骤如下:
auditorProvider()
方法返回一个AuditorAware<String>
对象UsernameAuditorAware
对象返回@Bean
注解@EnableJpaAuditing
注解PersistenceContext
类代码如下:
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;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider")
@EnableJpaRepositories(basePackages = {
"net.petrikainulainen.springdata.jpa.todo"
})
@EnableTransactionManagement
class PersistenceContext {
@Bean
AuditorAware<String> auditorProvider() {
return new UsernameAuditorAware();
}
@Bean
DateTimeProvider dateTimeProvider(DateTimeService dateTimeService) {
return new AuditingDateTimeProvider(dateTimeService);
}
}
因为我们只有一个AuditorAware
bean,所以Spring需要的时候会自动找到它,如果有多个bean,可以通过配置@EnableJpaAuditing
注解的auditorAwareRef属性值来设置。
如果使用XML配置,参照https://github.com/pkainulainen/spring-data-jpa-examples/blob/master/query-methods/src/main/resources/applicationContext-persistence.xml
下面修改实体类
有如下两个需求:
createdByUser
字段在实体第一次存储时被设置modifiedByUser
字段在实体以一次存储和以后被修改时被设置具体步骤如下:
创建一个String
类型的createdByUser
字段:
@Column
注解,配置字段名为created_by_user,非空@CreatedBy
注解,说明这个字段表示的是谁创建了这个实体创建一个String
类型的modifiedByUser
字段:
@Column
注解,配置字段名为modified_by_user,非空@LastModified
注解,说明这个字段表示的是谁最近修改了这个实体修改后的Todo
实体类如下:
import org.hibernate.annotations.Type;
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.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
import java.time.ZonedDateTime;
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "todos")
final class Todo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "created_by_user", nullable = false)
@CreatedBy
private String createdByUser;
@Column(name = "creation_time", nullable = false)
@Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
@CreatedDate
private ZonedDateTime creationTime;
@Column(name = "description", length = 500)
private String description;
@Column(name = "modified_by_user", nullable = false)
@LastModifiedBy
private String modifiedByUser;
@Column(name = "modification_time")
@Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
@LastModifiedDate
private ZonedDateTime modificationTime;
@Column(name = "title", nullable = false, length = 100)
private String title;
@Version
private long version;
}
如果有多个实体类需要审计,也可以将审计字段移到一个抽象基类中,就像下面:
import org.hibernate.annotations.Type;
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.MappedSuperClass
@EntityListeners(AuditingEntityListener.class)
@MappedSuperClass
public abstract class BaseEntity {
@Column(name = "created_by_user", nullable = false)
@CreatedBy
private String createdByUser;
@Column(name = "creation_time", nullable = false)
@Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
@CreatedDate
private ZonedDateTime creationTime;
@Column(name = "modified_by_user", nullable = false)
@LastModifiedBy
private String modifiedByUser;
@Column(name = "modification_time")
@Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
@LastModifiedDate
private ZonedDateTime modificationTime;
}
如果不想使用注解,可以实现Auditable
接口或者继承AbstractAuditable
类。Auditable
接口声明了所有的审计字段的getter和setter方法,AbstractAuditable
类提供了这些方法的实现,但有个劣势是你的实体类和Spring Data就产生了耦合
让我们看看为什么使用Spring Data JPA提供的审计支持而不是使用Java Persistence API提供的回调方法
我们还可以通过使用JPA的生命周期注解来标注一些方法,这些方法会在存储和更新时执行回调,例如:
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import javax.persistence.Column;
import javax.persistence.MappedSuperClass
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
@MappedSuperClass
public abstract class BaseEntity {
@Column(name = "created_by_user", nullable = false)
@CreatedBy
private String createdByUser;
@Column(name = "modified_by_user", nullable = false)
@LastModifiedBy
private String modifiedByUser;
@PrePersist
public void prePersist() {
String createdByUser = getUsernameOfAuthenticatedUser();
this.createdByUser = createdByUser;
this.modifiedByUser = createdByUser;
}
@PreUpdate
public void preUpdate() {
String modifiedByUser = getUsernameOfAuthenticatedUser();
this.modifiedByUser = modifiedByUser;
}
private String getUsernameOfAuthenticatedUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return null;
}
return ((User) authentication.getPrincipal()).getUsername();
}
}
即使这个方式比使用Spring Data JPA提供的基础架构更简单和直接一些,仍然有两个原因支持我们使用更复杂的解决方案:
本篇说了下面三个事:
AuditorAware<T>
接口获取用户信息Auditable
接口,或者继承AbstractAuditable
类来实现审计功能项目代码在作者的github上:https://github.com/pkainulainen/spring-data-jpa-examples/tree/master/query-methods
原文连接:http://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-auditing-part-two