Spring Data JPA教程:审计(二)

来源:互联网 发布:化学物质索引数据库 编辑:程序博客网 时间:2024/05/22 03:26

书接上文

本文解决前面两个问题中的第二个问题,我们将为实体加上创建者和修改者的信息

首先创建一个返回授权用户信息的组件

获取授权用户信息

Spring Data JPA使用AuditorAware<T>接口获取用户信息,AuditorAware接口的泛型参数T描述了实体类中审计人的类型

现在开始创建一个返回用户信息的类:

  1. 创建UsernameAuditorAware类实现AuditorAware接口,我们想存储String类型的用户名,所以参数T设置为String
  2. 实现getCurrentAuditor()方法:
    1. SecurityContext获取Authentication对象
    2. 如果得到的授权对象为null或者未经认证,返回null
    3. 返回username

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();    }}

配置Application Context

下面我们将UsernameAuditorAware配置成一个bean,修改PersistenceContext类,步骤如下:

  1. 创建auditorProvider()方法返回一个AuditorAware<String>对象
  2. 方法实现里new一个UsernameAuditorAware对象返回
  3. 给方法加上@Bean注解
  4. 加上@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"})@EnableTransactionManagementclass PersistenceContext {    @Bean    AuditorAware<String> auditorProvider() {        return new UsernameAuditorAware();    }    @Bean    DateTimeProvider dateTimeProvider(DateTimeService dateTimeService) {        return new AuditingDateTimeProvider(dateTimeService);    }}

因为我们只有一个AuditorAwarebean,所以Spring需要的时候会自动找到它,如果有多个bean,可以通过配置@EnableJpaAuditing注解的auditorAwareRef属性值来设置。

如果使用XML配置,参照https://github.com/pkainulainen/spring-data-jpa-examples/blob/master/query-methods/src/main/resources/applicationContext-persistence.xml

下面修改实体类

修改实体类

有如下两个需求:

  1. 确保createdByUser字段在实体第一次存储时被设置
  2. 确保modifiedByUser字段在实体以一次存储和以后被修改时被设置

具体步骤如下:

  1. 创建一个String类型的createdByUser字段:

    1. 加上@Column注解,配置字段名为created_by_user,非空
    2. 加上@CreatedBy注解,说明这个字段表示的是谁创建了这个实体
  2. 创建一个String类型的modifiedByUser字段:

    1. 加上@Column注解,配置字段名为modified_by_user,非空
    2. 加上@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)@MappedSuperClasspublic 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提供的回调方法

为什么使用Spring Data JPA提供的审计支持?

我们还可以通过使用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.MappedSuperClassimport javax.persistence.PrePersist;import javax.persistence.PreUpdate;@MappedSuperClasspublic 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提供的基础架构更简单和直接一些,仍然有两个原因支持我们使用更复杂的解决方案:

  • 使用回调方法会和Spring Security产生耦合
  • 如果我想用Spring Data JPA记录创建时间和修改时间的信息,而设置修改人还是用上面那种方式,用两种方式设置审计信息是没有什么意义的

总结一下

本篇说了下面三个事:

  • Spring Data JPA使用AuditorAware<T>接口获取用户信息
  • 我们可以通过注解,或者实现Auditable接口,或者继承AbstractAuditable类来实现审计功能
  • 通过回调lifecycle的方法比较简单,但劣势是我们的抽象基类和Spring Security产生了耦合

项目代码在作者的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

1 0
原创粉丝点击