3. 参考文档

3.1. 什么是 Spring Data Envers?

Spring Data Envers 在 Spring Data JPA 的存储库中提供典型的 Envers 查询。 它与其他 Spring Data 模块的不同之处在于,它总是与另一个 Spring Data 模块结合使用:Spring Data JPA。spring-doc.cn

3.2. 什么是 Envers?

Envers 是一个 Hibernate 模块,用于向 JPA 实体添加审计功能。 本文档假定您熟悉 Envers,就像 Spring Data Envers 依赖于正确配置 Envers 一样。spring-doc.cn

3.3. 配置

作为使用 Spring Data Envers 的起点,您需要一个在 Classpath 上具有 Spring Data JPA 的项目和一个额外的依赖项:spring-data-enversspring-doc.cn

<dependencies>

  <!-- other dependency elements omitted -->

  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-envers</artifactId>
    <version>2.6.10</version>
  </dependency>

</dependencies>

这也将作为 transent 依赖项引入项目。hibernate-enversspring-doc.cn

要启用 Spring Data Envers 和 Spring Data JPA,我们需要配置两个 bean 和一个特殊的:repositoryFactoryBeanClassspring-doc.cn

@Configuration
@EnableEnversRepositories
@EnableTransactionManagement
public class EnversDemoConfiguration {

	@Bean
	public DataSource dataSource() {

		EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
		return builder.setType(EmbeddedDatabaseType.HSQL).build();
	}

	@Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactory() {

		HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
		vendorAdapter.setGenerateDdl(true);

		LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
		factory.setJpaVendorAdapter(vendorAdapter);
		factory.setPackagesToScan("example.springdata.jpa.envers");
		factory.setDataSource(dataSource());
		return factory;
	}

	@Bean
	public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {

		JpaTransactionManager txManager = new JpaTransactionManager();
		txManager.setEntityManagerFactory(entityManagerFactory);
		return txManager;
	}
}

要实际使用 Spring Data Envers,请将其添加为扩展接口,将其放入RevisionRepository中:spring-doc.cn

interface PersonRepository
    extends CrudRepository<Person, Long>,
    RevisionRepository<Person, Long, Long> (1)
{}
1 第一个类型参数 () 表示实体类型,第二个 () 表示 id 属性的类型,最后一个 () 是修订号的类型。 对于默认配置中的 Envers,修订号参数应为 或 。PersonLongLongIntegerLong

该存储库的实体必须是启用了 Envers 审计的实体(即,它必须具有注释):@Auditedspring-doc.cn

@Entity
@Audited
class Person {

	@Id @GeneratedValue
	Long id;
	String name;
	@Version Long version;
}

3.4. 使用

您现在可以使用 methods from 来查询实体的修订版,如以下测试用例所示:RevisionRepositoryspring-doc.cn

@ExtendWith(SpringExtension.class)
@Import(EnversDemoConfiguration.class) (1)
class EnversIntegrationTests {

	final PersonRepository repository;
	final TransactionTemplate tx;

	EnversIntegrationTests(@Autowired PersonRepository repository, @Autowired PlatformTransactionManager tm) {
		this.repository = repository;
		this.tx = new TransactionTemplate(tm);
	}

	@Test
	void testRepository() {

		Person updated = preparePersonHistory();

		Revisions<Long, Person> revisions = repository.findRevisions(updated.id);

		Iterator<Revision<Long, Person>> revisionIterator = revisions.iterator();

		checkNextRevision(revisionIterator, "John", RevisionType.INSERT);
		checkNextRevision(revisionIterator, "Jonny", RevisionType.UPDATE);
		checkNextRevision(revisionIterator, null, RevisionType.DELETE);
		assertThat(revisionIterator.hasNext()).isFalse();

	}

	/**
    * Checks that the next element in the iterator is a Revision entry referencing a Person
    * with the given name after whatever change brought that Revision into existence.
    * <p>
    * As a side effect the Iterator gets advanced by one element.
    *
    * @param revisionIterator the iterator to be tested.
    * @param name the expected name of the Person referenced by the Revision.
    * @param revisionType the type of the revision denoting if it represents an insert, update or delete.
    */
	private void checkNextRevision(Iterator<Revision<Long, Person>> revisionIterator, String name,
			RevisionType revisionType) {

		assertThat(revisionIterator.hasNext()).isTrue();
		Revision<Long, Person> revision = revisionIterator.next();
		assertThat(revision.getEntity().name).isEqualTo(name);
		assertThat(revision.getMetadata().getRevisionType()).isEqualTo(revisionType);
	}

	/**
    * Creates a Person with a couple of changes so it has a non-trivial revision history.
    * @return the created Person.
    */
	private Person preparePersonHistory() {

		Person john = new Person();
		john.setName("John");

		// create
		Person saved = tx.execute(__ -> repository.save(john));
		assertThat(saved).isNotNull();

		saved.setName("Jonny");

		// update
		Person updated = tx.execute(__ -> repository.save(saved));
		assertThat(updated).isNotNull();

		// delete
		tx.executeWithoutResult(__ -> repository.delete(updated));
		return updated;
	}
}
1 这引用了前面介绍的 application context configuration (在 Configuration 部分中)。