Spring Data JPA审计示例

时间:2020-01-09 10:44:33  来源:igfitidea点击:

在本文中,我们将了解如何配置Spring Data JPA以自动存储任何实体的审核信息,例如创建日期,创建日期,修改日期和修改日期。

Spring Data审核支持

Spring Data提供支持来跟踪谁创建或者更改了实体以及发生时间。要使用此审计功能,我们需要为实体类配备审计元数据,该审计元数据可以使用注释或者通过实现接口来定义。

使用的注释如下:

@CreatedBy –捕获创建实体的用户。
@LastModifiedBy –捕获修改实体的用户。
@CreatedDate –捕获创建实体时的时间点。
@LastModifiedDate –捕获实体上次修改的时间点。

如果我们想了解如何创建Maven项目,请查看此文章在Eclipse中使用Maven创建Java项目。

有关设置Spring Data JPA项目和所需的Maven依赖关系的信息,请查看此文章Spring Data JPA示例

除了该文章中提到的依赖项,我们还需要在classpath上使用spring-aspects.jar来审核功能,为此,我们需要添加以下依赖项。

<dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-aspects</artifactId>
     <version>${spring.version}</version>
</dependency>

Spring数据注释@ CreatedBy,@ LastModifiedBy,@ CreatedDate,@ LastModifiedDate

在示例中,我们将看到如何配置Spring Data JPA以自动存储任何实体的审核信息,例如创建日期,创建日期,修改日期和修改日期。

对于示例,使用的实体为post,并且该表具有post_title和post_content列以存储标题和内容。除此以外,有关正在创建或者修改任何记录的用户以及创建和修改时间的信息也将保留。

数据库表

可以使用以下查询创建用于此Spring数据JPA审计示例的MySQL DB表。

CREATE TABLE `post` (
  `post_id` int(11) NOT NULL AUTO_INCREMENT,
  `post_title` varchar(100) NOT NULL,
  `post_content` varchar(250) DEFAULT NULL,
  `created_by` varchar(45) DEFAULT NULL,
  `created_date` datetime DEFAULT NULL,
  `modified_by` varchar(45) DEFAULT NULL,
  `modified_date` datetime DEFAULT NULL,
  PRIMARY KEY (`post_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

JPA实体类

应该有一个实体类,该实体类映射到DB中的post表。更好的做法是将与审计相关的字段保留在单独的类中,需要审计信息的实体可以扩展该类。这样,其他实体也可以重用超类。

因此,存在一个类审计,其注释为@MappedSuperclass。映射的超类没有为其定义单独的表。它的映射信息适用于从其继承的实体。

Spring Data JPA提供了一个称为AuditingEntityListener的实体侦听器,可用于触发捕获审计信息。 AuditingEntityListener类具有以@PrePersist和@PreUpdate注释注释的回调方法,分别为持久事件和更新事件触发了这些方法。

我们可以使用@EntityListeners注释为每个实体启用AuditingEntityListener。我们还可以使用@EntityListeners注释指定自己的自定义侦听器类。

import java.util.Date;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
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;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Audit<T> {
	@CreatedBy
	@Column(name="created_by")
	protected T createdBy;
	@LastModifiedBy
	@Column(name="modified_by")
	protected T modifiedBy;
	@CreatedDate
	@Temporal(TemporalType.TIMESTAMP)
	@Column(name="created_date")
	protected Date createdDate;
	@LastModifiedDate
	@Temporal(TemporalType.TIMESTAMP)
	@Column(name="modified_date")
	protected Date modifiedDate;
	public T getCreatedBy() {
		return createdBy;
	}
	public void setCreatedBy(T createdBy) {
		this.createdBy = createdBy;
	}
	public T getModifiedBy() {
		return modifiedBy;
	}
	public void setModifiedBy(T modifiedBy) {
		this.modifiedBy = modifiedBy;
	}
	public Date getCreatedDate() {
		return createdDate;
	}
	public void setCreatedDate(Date createdDate) {
		this.createdDate = createdDate;
	}
	public Date getModifiedDate() {
		return modifiedDate;
	}
	public void setModifiedDate(Date modifiedDate) {
		this.modifiedDate = modifiedDate;
	}	
}

如上所示,超类具有捕获创建和修改数据的字段,这些字段映射到适当的列。

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="post")
public class Post extends Audit<String> {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name="post_id")
	private int id;
	@Column(name="post_title")
	private String postTitle;
	@Column(name="post_content")
	private String content;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getPostTitle() {
		return postTitle;
	}
	public void setPostTitle(String postTitle) {
		this.postTitle = postTitle;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	
	@Override
	public String toString() {
		
		return "Id= "+ getId() + " Title= " + getPostTitle() + " Content= "+ getContent()
		+ " createdBy= " + getCreatedBy() + " modifiedBy= " + getModifiedBy() 
		+ " createdDate= " + getCreatedDate() + " modifiedDate= " + getModifiedDate() ;
	}
}

AuditorAware界面

如果我们使用@CreatedBy或者@LastModifiedBy注释,则审核基础结构需要捕获有关当前用户的信息。为此,我们必须实现AuditorAware <T>接口基础结构,以告知当前与该应用程序交互的用户或者系统是谁。

对于我们的示例,我们使用当前使用system属性登录的用户。

import java.util.Optional;
import org.springframework.data.domain.AuditorAware;

public class LoggedInUserAuditorAwareImpl implements AuditorAware<String>{

	@Override
	public Optional<String> getCurrentAuditor() {
		return Optional.of(System.getProperty("user.name"));
	}
}

Spring Data JPA存储库

import org.springframework.data.repository.CrudRepository;
import com.theitroad.springproject.model.Post;

public interface PostRepository extends CrudRepository<Post, Integer>{

}

我们可以看到PostRepository接口扩展了CrudRepository,它使用域类进行管理(在本例中为Post),并且将域类的id类型作为类型参数。

服务等级

在服务层,我们将调用存储库方法。请注意,必须将存储库实例注入服务类中。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.theitroad.springproject.dao.PostRepository;
import com.theitroad.springproject.model.Post;

@Service
public class PostService {
  @Autowired
  private PostRepository repository;
  public Post addPost(Post post) {
    return repository.save(post);
  }

  public Post getPostById(int id) {
    return repository.findById(id).get();
  }
}

Java Config类

在此Spring数据JPA审计示例中,使用Java配置,因此使用@Configuration注释对类进行注释。

@ComponentScan注释配置组件扫描。

@EnableJpaRepositories注释启用JPA存储库。带有注释的值提供了用于扫描存储库的软件包。

@EnableTransactionManagement注释启用Spring的注释驱动的事务管理功能。

@EnableJpaAuditing注释启用JPA中的审核。

要设置DataSource DB属性是从属性文件中读取的,请使用@PropertySource注释配置属性文件的路径。

在此Java配置类中,我们设置了EntityManagerFactory并将Hibernate用作持久性提供程序。对于AuditorAware实现,也有一个bean定义。

@Configuration
@ComponentScan(basePackages = "com.theitroad.springproject")
@EnableJpaRepositories(basePackages = "com.theitroad.springproject.dao")
@EnableTransactionManagement
@EnableJpaAuditing
@PropertySource("classpath:config/db.properties")
public class JPAConfig {
  @Autowired
  private Environment env;
  @Bean
  public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
    factory.setJpaVendorAdapter(vendorAdapter);
    factory.setPackagesToScan("com.theitroad.springproject.model");
    factory.setDataSource(dataSource());
    factory.setJpaProperties(hibernateProperties());
    return factory;
  }

  @Bean
  public DataSource dataSource() {
    DriverManagerDataSource ds = new DriverManagerDataSource();
    ds.setDriverClassName(env.getProperty("db.driverClassName"));
    ds.setUrl(env.getProperty("db.url"));
    ds.setUsername(env.getProperty("db.username"));
    ds.setPassword(env.getProperty("db.password"));
    return ds;
  }

  Properties hibernateProperties() {
    Properties properties = new Properties();
    properties.setProperty("hibernate.dialect", env.getProperty("hibernate.sqldialect"));
    properties.setProperty("hibernate.show_sql", env.getProperty("hibernate.showsql"));
    return properties;
  }

  @Bean
  public PlatformTransactionManager transactionManager() {
    JpaTransactionManager txManager = new JpaTransactionManager();
    txManager.setEntityManagerFactory(entityManagerFactory().getObject());
    return txManager;
  }

  @Bean
  public AuditorAware<String> auditorProvider() {
    return new LoggedInUserAuditorAwareImpl();
  }
}

db.properties文件

db.driverClassName=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/theitroad
db.username=
db.password=
hibernate.sqldialect=org.hibernate.dialect.MySQLDialect
hibernate.showsql=true

测试示例

以下具有main方法的类用于测试示例。最初,发布实例是持久的。从查询中可以看到,还插入了审计信息(created_by,created_date,modified_by,modified_date)。

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import com.theitroad.springproject.model.Post;
import com.theitroad.springproject.service.PostService;

public class App {
  public static void main(String[] args) {
    AbstractApplicationContext context = new AnnotationConfigApplicationContext(JPAConfig.class);
    PostService postService =  context.getBean("postService", PostService.class);
    Post post = new Post();
    post.setPostTitle("A Post on Spring Data JPA");
    post.setContent("Spring Data JPA reduces boiler plate code");
    postService.addPost(post);
    //postService.updatePost(1);
    
    context.close();
  }
}

从日志生成的查询

Hibernate: insert into post (created_by, created_date, modified_by, modified_date, post_content, post_title) values (?, ?, ?, ?, ?, ?)

取得记录
获取持久化的记录。

public class App {
  public static void main(String[] args) {
    AbstractApplicationContext context = new AnnotationConfigApplicationContext(JPAConfig.class);
    PostService postService =  context.getBean("postService", PostService.class);

    Post post = postService.getPostById(1);
    System.out.println("Post- "+ post);
    context.close();
  }
}

生成查询并获取记录

Hibernate: select post0_.post_id as post_id1_1_0_, post0_.created_by as created_2_1_0_, post0_.created_date as created_3_1_0_, post0_.modified_by as modified4_1_0_, post0_.modified_date as modified5_1_0_, post0_.post_content as post_con6_1_0_, post0_.post_title as post_tit7_1_0_ from post post0_ where post0_.post_id=?

Post- Id= 1 Title= A Post on Spring Data JPA Content= Spring Data JPA reduces boiler plate code createdBy= theitroad modifiedBy= theitroad createdDate= 2019-09-30 11:16:47.0 modifiedDate= 2019-09-30 11:16:47.0

更新记录

public class App {
  public static void main(String[] args) {
    AbstractApplicationContext context = new AnnotationConfigApplicationContext(JPAConfig.class);
    PostService postService =  context.getBean("postService", PostService.class);
    
    Post post = postService.getPostById(1);
    post.setPostTitle("New Title");
    postService.addPost(post);
    post = postService.getPostById(1);
    System.out.println("Post- " + post);
    context.close();
  }
}

如我们所见,现在获取记录时,修改日期会更改。

Post- Id= 1 Title= New Title Content= Spring Data JPA reduces boiler plate code createdBy= theitroad modifiedBy= theitroad createdDate= 2019-09-30 11:16:47.0 modifiedDate= 2019-09-30 12:08:50.0