|
| 1 | +[[envers.what]] |
| 2 | +== What is Spring Data Envers |
| 3 | + |
| 4 | +Spring Data Envers differs from other Spring Data modules in that it is always used in combination with another Spring Data Module: Spring Data JPA. |
| 5 | +It makes typical Envers queries available in repositories for Spring Data JPA. |
| 6 | + |
| 7 | +== What is Envers? |
| 8 | + |
| 9 | +Envers is a Hibernate module which adds auditing capabilities to JPA entities. |
| 10 | +This documentation assumes you are familiar with Envers just as Spring Data Envers relies on Envers being properly configured. |
| 11 | + |
| 12 | +[[envers.configuration]] |
| 13 | +== Configuration |
| 14 | + |
| 15 | +As a starting point for using Spring Data Envers you need a project with Spring Data JPA on the classpath and an additional `spring-data-envers` dependency. |
| 16 | + |
| 17 | +[source,xml,subs="+attributes"] |
| 18 | +---- |
| 19 | +<dependencies> |
| 20 | +
|
| 21 | + <!-- other dependency elements omitted --> |
| 22 | +
|
| 23 | + <dependency> |
| 24 | + <groupId>org.springframework.data</groupId> |
| 25 | + <artifactId>spring-data-envers</artifactId> |
| 26 | + <version>{version}</version> |
| 27 | + </dependency> |
| 28 | +
|
| 29 | +</dependencies> |
| 30 | +---- |
| 31 | + |
| 32 | +This will also bring `hibernate-envers` into the project as a transient dependency. |
| 33 | + |
| 34 | +In order to enable Spring Data Envers and Spring Data JPA we need to configure two beans and a special `repositoryFactoryBeanClass` |
| 35 | + |
| 36 | +==== |
| 37 | +[source,java] |
| 38 | +---- |
| 39 | +@Configuration |
| 40 | +@EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class) // <1> |
| 41 | +@EnableTransactionManagement |
| 42 | +public class EnversDemoConfiguration { |
| 43 | +@Bean |
| 44 | +public DataSource dataSource() { |
| 45 | +
|
| 46 | + EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); |
| 47 | + return builder.setType(EmbeddedDatabaseType.HSQL).build(); |
| 48 | + } |
| 49 | +
|
| 50 | + @Bean |
| 51 | + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { |
| 52 | +
|
| 53 | + HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); |
| 54 | + vendorAdapter.setGenerateDdl(true); |
| 55 | +
|
| 56 | + LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); |
| 57 | + factory.setJpaVendorAdapter(vendorAdapter); |
| 58 | + factory.setPackagesToScan("example.springdata.jpa.envers"); |
| 59 | + factory.setDataSource(dataSource()); |
| 60 | + return factory; |
| 61 | + } |
| 62 | +
|
| 63 | + @Bean |
| 64 | + public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { |
| 65 | +
|
| 66 | + JpaTransactionManager txManager = new JpaTransactionManager(); |
| 67 | + txManager.setEntityManagerFactory(entityManagerFactory); |
| 68 | + return txManager; |
| 69 | + } |
| 70 | +} |
| 71 | +---- |
| 72 | +<1> This is the only difference to a normal Spring Data JPA configuration. `EnversRevisionRepositoryFactoryBean` ensures implementations of the methods in `RevisionRepository` are available. |
| 73 | +==== |
| 74 | + |
| 75 | +In order to actually use Spring Data Envers make one or more repositories into {spring-data-commons-javadoc-base}/org/springframework/data/repository/history/RevisionRepository.html[`RevisionRepository`] by adding it as an extended interface. |
| 76 | + |
| 77 | +==== |
| 78 | +[source,java] |
| 79 | +---- |
| 80 | +public interface PersonRepository |
| 81 | + extends CrudRepository<Person, Long>, |
| 82 | + RevisionRepository<Person, Long, Long> // <1> |
| 83 | +{} |
| 84 | +---- |
| 85 | +<1> The first type parameter `Person` denotes the entity type, the second (`Long`) the type of the id property and the last one (`Long`) is the type of the revision number. |
| 86 | +For Envers in default configuration this should be `Integer` or `Long`. |
| 87 | +==== |
| 88 | + |
| 89 | +The entity for that repository must be an entity with Envers auditing enabled, i.e. it has an `@Audited` annotation. |
| 90 | + |
| 91 | +[source,java] |
| 92 | +---- |
| 93 | +@Entity |
| 94 | +@Audited |
| 95 | +public class Person { |
| 96 | +
|
| 97 | + @Id @GeneratedValue |
| 98 | + Long id; |
| 99 | + String name; |
| 100 | + @Version Long version; |
| 101 | +} |
| 102 | +---- |
| 103 | + |
| 104 | +[[envers.usage]] |
| 105 | +== Usage |
| 106 | + |
| 107 | +You may now use the methods from `RevisionRepository` to query the revisions of the entity as demonstrated in the following test case. |
| 108 | + |
| 109 | +==== |
| 110 | +[source,java] |
| 111 | +---- |
| 112 | +@ExtendWith(SpringExtension.class) |
| 113 | +@Import(EnversDemoConfiguration.class) // <1> |
| 114 | +public class EnversIntegrationTests { |
| 115 | +
|
| 116 | + final PersonRepository repository; |
| 117 | + final TransactionTemplate tx; |
| 118 | +
|
| 119 | + EnversIntegrationTests(@Autowired PersonRepository repository, @Autowired PlatformTransactionManager tm) { |
| 120 | + this.repository = repository; |
| 121 | + this.tx = new TransactionTemplate(tm); |
| 122 | + } |
| 123 | +
|
| 124 | + @Test |
| 125 | + void testRepository() { |
| 126 | +
|
| 127 | + Person updated = preparePersonHistory(); |
| 128 | +
|
| 129 | + Revisions<Long, Person> revisions = repository.findRevisions(updated.id); |
| 130 | +
|
| 131 | + Iterator<Revision<Long, Person>> revisionIterator = revisions.iterator(); |
| 132 | +
|
| 133 | + checkNextRevision(revisionIterator, "John", RevisionType.INSERT); |
| 134 | + checkNextRevision(revisionIterator, "Jonny", RevisionType.UPDATE); |
| 135 | + checkNextRevision(revisionIterator, null, RevisionType.DELETE); |
| 136 | + assertThat(revisionIterator.hasNext()).isFalse(); |
| 137 | +
|
| 138 | + } |
| 139 | +
|
| 140 | + /** |
| 141 | + * Checks that the next element in the iterator is a Revision entry referencing a Person |
| 142 | + * with the given name after whatever change brought that Revision into existence. |
| 143 | + * <p> |
| 144 | + * As a side effect the Iterator gets advanced by one element. |
| 145 | + * |
| 146 | + * @param revisionIterator the iterator to be tested. |
| 147 | + * @param name the expected name of the Person referenced by the Revision. |
| 148 | + * @param revisionType the type of the revision denoting if it represents an insert, update or delete. |
| 149 | + */ |
| 150 | + private void checkNextRevision(Iterator<Revision<Long, Person>> revisionIterator, String name, |
| 151 | + RevisionType revisionType) { |
| 152 | +
|
| 153 | + assertThat(revisionIterator.hasNext()).isTrue(); |
| 154 | + Revision<Long, Person> revision = revisionIterator.next(); |
| 155 | + assertThat(revision.getEntity().name).isEqualTo(name); |
| 156 | + assertThat(revision.getMetadata().getRevisionType()).isEqualTo(revisionType); |
| 157 | + } |
| 158 | +
|
| 159 | + /** |
| 160 | + * Creates a Person with a couple of changes so it has a non-trivial revision history. |
| 161 | + * @return the created Person. |
| 162 | + */ |
| 163 | + private Person preparePersonHistory() { |
| 164 | +
|
| 165 | + Person john = new Person(); |
| 166 | + john.setName("John"); |
| 167 | +
|
| 168 | + // create |
| 169 | + Person saved = tx.execute(__ -> repository.save(john)); |
| 170 | + assertThat(saved).isNotNull(); |
| 171 | +
|
| 172 | + saved.setName("Jonny"); |
| 173 | +
|
| 174 | + // update |
| 175 | + Person updated = tx.execute(__ -> repository.save(saved)); |
| 176 | + assertThat(updated).isNotNull(); |
| 177 | +
|
| 178 | + // delete |
| 179 | + tx.executeWithoutResult(__ -> repository.delete(updated)); |
| 180 | + return updated; |
| 181 | + } |
| 182 | +} |
| 183 | +---- |
| 184 | +<1> This references the application context configuration presented above |
| 185 | +==== |
| 186 | + |
| 187 | +[[envers.resources]] |
| 188 | +== Further Resources |
| 189 | + |
| 190 | +There is a https://github.com/spring-projects/spring-data-examples[Spring Data Envers example in the Spring Data Examples repository] that you can download and play around with to get a feel for how the library works. |
| 191 | + |
| 192 | +You should also check out the https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/history/RevisionRepository.html[Javadoc for `RevisionRepository`] and related classes. |
| 193 | + |
| 194 | +Questions are best asked at https://stackoverflow.com/questions/tagged/spring-data-envers[Stackoverflow using the `spring-data-envers` tag]. |
| 195 | + |
| 196 | +The https://github.com/spring-projects/spring-data-envers[source code and issue tracker for Spring Data Envers is hosted at GitHub]. |
| 197 | + |
| 198 | + |
0 commit comments