DATAJDBC-204 - Support for annotation based auditing.
When enabled by using @EnableJdbcAuditing entities will get properties annotated with * @CreatedBy * @CreatedDate * @LastModifiedBy * @LastModifiedDate updated when saved. Original pull request: #64.
This commit is contained in:
committed by
Jens Schauder
parent
9873db6fff
commit
92cd333854
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.domain.support;
|
||||
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.data.auditing.AuditingHandler;
|
||||
import org.springframework.data.jdbc.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Spring JDBC event listener to capture auditing information on persisting and updating entities.
|
||||
* <p>
|
||||
* You can enable this class just a matter of activating auditing using {@link org.springframework.data.jdbc.repository.config.EnableJdbcAuditing} in your Spring config:
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableJdbcRepositories
|
||||
* @EnableJdbcAuditing
|
||||
* class JdbcRepositoryConfig {
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Kazuki Shimizu
|
||||
* @see org.springframework.data.jdbc.repository.config.EnableJdbcAuditing
|
||||
* @since 1.0
|
||||
*/
|
||||
public class JdbcAuditingEventListener implements ApplicationListener<BeforeSaveEvent> {
|
||||
|
||||
@Nullable
|
||||
private AuditingHandler handler;
|
||||
|
||||
/**
|
||||
* Configures the {@link AuditingHandler} to be used to set the current auditor on the domain types touched.
|
||||
*
|
||||
* @param auditingHandler must not be {@literal null}.
|
||||
*/
|
||||
public void setAuditingHandler(ObjectFactory<AuditingHandler> auditingHandler) {
|
||||
Assert.notNull(auditingHandler, "AuditingHandler must not be null!");
|
||||
this.handler = auditingHandler.getObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @param event a notification event for indicating before save
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(BeforeSaveEvent event) {
|
||||
if (handler != null) {
|
||||
event.getOptionalEntity().ifPresent(entity -> {
|
||||
if (event.getId().getOptionalValue().isPresent()) {
|
||||
handler.markModified(entity);
|
||||
} else {
|
||||
handler.markCreated(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository.config;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
|
||||
/**
|
||||
* Annotation to enable auditing in JDBC via annotation configuration.
|
||||
*
|
||||
* If you use the auditing feature, you should be configures beans of Spring Data JDBC
|
||||
* using {@link org.springframework.data.jdbc.repository.config.EnableJdbcRepositories} in your Spring config:
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableJdbcRepositories
|
||||
* @EnableJdbcAuditing
|
||||
* class JdbcRepositoryConfig {
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Note: This feature cannot use to a entity that implements {@link org.springframework.data.domain.Auditable}
|
||||
* because the Spring Data JDBC does not support an {@link java.util.Optional} property yet.
|
||||
* </p>
|
||||
*
|
||||
* @see EnableJdbcRepositories
|
||||
* @author Kazuki Shimizu
|
||||
* @since 1.0
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import(JdbcAuditingRegistrar.class)
|
||||
public @interface EnableJdbcAuditing {
|
||||
|
||||
/**
|
||||
* Configures the {@link AuditorAware} bean to be used to lookup the current principal.
|
||||
*
|
||||
* @return
|
||||
* @see AuditorAware
|
||||
*/
|
||||
String auditorAwareRef() default "";
|
||||
|
||||
/**
|
||||
* Configures whether the creation and modification dates are set.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean setDates() default true;
|
||||
|
||||
/**
|
||||
* Configures whether the entity shall be marked as modified on creation.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean modifyOnCreate() default true;
|
||||
|
||||
/**
|
||||
* Configures a {@link DateTimeProvider} bean name that allows customizing the {@link java.time.LocalDateTime} to be
|
||||
* used for setting creation and modification dates.
|
||||
*
|
||||
* @return
|
||||
* @see DateTimeProvider
|
||||
*/
|
||||
String dateTimeProviderRef() default "";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
|
||||
import org.springframework.data.auditing.config.AuditingConfiguration;
|
||||
import org.springframework.data.config.ParsingUtils;
|
||||
import org.springframework.data.jdbc.domain.support.JdbcAuditingEventListener;
|
||||
|
||||
/**
|
||||
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableJdbcAuditing} annotation.
|
||||
*
|
||||
* @see EnableJdbcAuditing
|
||||
* @author Kazuki Shimizu
|
||||
* @since 1.0
|
||||
*/
|
||||
class JdbcAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @return return the {@link EnableJdbcAuditing}
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
|
||||
*/
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotation() {
|
||||
return EnableJdbcAuditing.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @return return "{@literal jdbcAuditingHandler}"
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName()
|
||||
*/
|
||||
@Override
|
||||
protected String getAuditingHandlerBeanName() {
|
||||
return "jdbcAuditingHandler";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration)
|
||||
*/
|
||||
@Override
|
||||
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
|
||||
BeanDefinitionBuilder builder = super.getAuditHandlerBeanDefinitionBuilder(configuration);
|
||||
return builder.addConstructorArgReference("jdbcMappingContext");
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the bean definition of {@link JdbcAuditingEventListener}.
|
||||
* {@inheritDoc}
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListenerBeanDefinition(BeanDefinition, BeanDefinitionRegistry)
|
||||
*/
|
||||
@Override
|
||||
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
|
||||
BeanDefinitionRegistry registry) {
|
||||
Class<?> listenerClass = JdbcAuditingEventListener.class;
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(listenerClass);
|
||||
builder.addPropertyValue("auditingHandler",
|
||||
ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), null));
|
||||
registerInfrastructureBeanWithId(builder.getRawBeanDefinition(), listenerClass.getName(), registry);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.data.jdbc.mapping.model.NamingStrategy;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests the {@link EnableJdbcAuditing} annotation.
|
||||
*
|
||||
* @author Kazuki Shimizu
|
||||
*/
|
||||
public class EnableJdbcAuditingHsqlIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void auditForAnnotatedEntity() throws InterruptedException {
|
||||
try (ConfigurableApplicationContext context =
|
||||
new AnnotationConfigApplicationContext(TestConfiguration.class, AuditingConfiguration.class)) {
|
||||
|
||||
AuditingAnnotatedDummyEntityRepository repository = context.getBean(AuditingAnnotatedDummyEntityRepository.class);
|
||||
|
||||
AuditingConfiguration.currentAuditor = "user01";
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
AuditingAnnotatedDummyEntity entity = new AuditingAnnotatedDummyEntity();
|
||||
entity.setName("Spring Data");
|
||||
repository.save(entity);
|
||||
|
||||
assertThat(entity.id).isNotNull();
|
||||
assertThat(entity.getCreatedBy()).isEqualTo("user01");
|
||||
assertThat(entity.getCreatedDate()).isAfter(now);
|
||||
assertThat(entity.getLastModifiedBy()).isEqualTo("user01");
|
||||
assertThat(entity.getLastModifiedDate()).isAfterOrEqualTo(entity.getCreatedDate());
|
||||
assertThat(entity.getLastModifiedDate()).isAfter(now);
|
||||
assertThat(repository.findById(entity.id).get()).isEqualTo(entity);
|
||||
|
||||
LocalDateTime beforeCreatedDate = entity.getCreatedDate();
|
||||
LocalDateTime beforeLastModifiedDate = entity.getLastModifiedDate();
|
||||
|
||||
TimeUnit.MILLISECONDS.sleep(100);
|
||||
AuditingConfiguration.currentAuditor = "user02";
|
||||
|
||||
entity.setName("Spring Data JDBC");
|
||||
repository.save(entity);
|
||||
|
||||
assertThat(entity.getCreatedBy()).isEqualTo("user01");
|
||||
assertThat(entity.getCreatedDate()).isEqualTo(beforeCreatedDate);
|
||||
assertThat(entity.getLastModifiedBy()).isEqualTo("user02");
|
||||
assertThat(entity.getLastModifiedDate()).isAfter(beforeLastModifiedDate);
|
||||
assertThat(repository.findById(entity.id).get()).isEqualTo(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noAnnotatedEntity() {
|
||||
try (ConfigurableApplicationContext context =
|
||||
new AnnotationConfigApplicationContext(TestConfiguration.class, AuditingConfiguration.class)) {
|
||||
|
||||
DummyEntityRepository repository = context.getBean(DummyEntityRepository.class);
|
||||
|
||||
DummyEntity entity = new DummyEntity();
|
||||
entity.setName("Spring Data");
|
||||
repository.save(entity);
|
||||
|
||||
assertThat(entity.id).isNotNull();
|
||||
assertThat(repository.findById(entity.id).get()).isEqualTo(entity);
|
||||
|
||||
entity.setName("Spring Data JDBC");
|
||||
repository.save(entity);
|
||||
|
||||
assertThat(repository.findById(entity.id).get()).isEqualTo(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customizeEnableJdbcAuditingAttributes() {
|
||||
// Test for 'auditorAwareRef', 'dateTimeProviderRef' and 'modifyOnCreate'
|
||||
try (ConfigurableApplicationContext context =
|
||||
new AnnotationConfigApplicationContext(TestConfiguration.class, CustomizeAuditingConfiguration1.class)) {
|
||||
AuditingAnnotatedDummyEntityRepository repository = context.getBean(AuditingAnnotatedDummyEntityRepository.class);
|
||||
|
||||
LocalDateTime currentDateTime = LocalDate.of(2018, 4, 14).atStartOfDay();
|
||||
CustomizeAuditingConfiguration1.currentDateTime = currentDateTime;
|
||||
|
||||
AuditingAnnotatedDummyEntity entity = new AuditingAnnotatedDummyEntity();
|
||||
entity.setName("Spring Data JDBC");
|
||||
repository.save(entity);
|
||||
|
||||
assertThat(entity.id).isNotNull();
|
||||
assertThat(entity.getCreatedBy()).isEqualTo("custom user");
|
||||
assertThat(entity.getCreatedDate()).isEqualTo(currentDateTime);
|
||||
assertThat(entity.getLastModifiedBy()).isNull();
|
||||
assertThat(entity.getLastModifiedDate()).isNull();
|
||||
}
|
||||
// Test for 'setDates'
|
||||
try (ConfigurableApplicationContext context =
|
||||
new AnnotationConfigApplicationContext(TestConfiguration.class, CustomizeAuditingConfiguration2.class)) {
|
||||
AuditingAnnotatedDummyEntityRepository repository = context.getBean(AuditingAnnotatedDummyEntityRepository.class);
|
||||
|
||||
AuditingAnnotatedDummyEntity entity = new AuditingAnnotatedDummyEntity();
|
||||
entity.setName("Spring Data JDBC");
|
||||
repository.save(entity);
|
||||
|
||||
assertThat(entity.id).isNotNull();
|
||||
assertThat(entity.getCreatedBy()).isEqualTo("user");
|
||||
assertThat(entity.getCreatedDate()).isNull();
|
||||
assertThat(entity.getLastModifiedBy()).isEqualTo("user");
|
||||
assertThat(entity.getLastModifiedDate()).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface AuditingAnnotatedDummyEntityRepository extends CrudRepository<AuditingAnnotatedDummyEntity, Long> {
|
||||
}
|
||||
|
||||
@Data
|
||||
static class AuditingAnnotatedDummyEntity {
|
||||
@Id
|
||||
private Long id;
|
||||
private String name;
|
||||
@CreatedBy
|
||||
private String createdBy;
|
||||
@CreatedDate
|
||||
private LocalDateTime createdDate;
|
||||
@LastModifiedBy
|
||||
private String lastModifiedBy;
|
||||
@LastModifiedDate
|
||||
private LocalDateTime lastModifiedDate;
|
||||
}
|
||||
|
||||
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
|
||||
}
|
||||
|
||||
@Data
|
||||
static class DummyEntity {
|
||||
@Id
|
||||
private Long id;
|
||||
private String name;
|
||||
}
|
||||
|
||||
@ComponentScan("org.springframework.data.jdbc.testing")
|
||||
@EnableJdbcRepositories(considerNestedRepositories = true)
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
Class<?> testClass() {
|
||||
return EnableJdbcAuditingHsqlIntegrationTests.class;
|
||||
}
|
||||
|
||||
@Bean
|
||||
NamingStrategy namingStrategy() {
|
||||
return new NamingStrategy() {
|
||||
public String getTableName(Class<?> type) {
|
||||
return "DummyEntity";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableJdbcAuditing
|
||||
static class AuditingConfiguration {
|
||||
private static String currentAuditor;
|
||||
@Bean
|
||||
AuditorAware<String> auditorAware() {
|
||||
return () -> Optional.ofNullable(currentAuditor);
|
||||
}
|
||||
}
|
||||
|
||||
@EnableJdbcAuditing(auditorAwareRef = "customAuditorAware", dateTimeProviderRef = "customDateTimeProvider", modifyOnCreate = false)
|
||||
static class CustomizeAuditingConfiguration1 {
|
||||
private static LocalDateTime currentDateTime;
|
||||
@Bean
|
||||
@Primary
|
||||
AuditorAware<String> auditorAware() {
|
||||
return () -> Optional.of("default user");
|
||||
}
|
||||
@Bean
|
||||
AuditorAware<String> customAuditorAware() {
|
||||
return () -> Optional.of("custom user");
|
||||
}
|
||||
@Bean
|
||||
DateTimeProvider customDateTimeProvider() {
|
||||
return () -> Optional.ofNullable(currentDateTime);
|
||||
}
|
||||
}
|
||||
|
||||
@EnableJdbcAuditing(setDates = false)
|
||||
static class CustomizeAuditingConfiguration2 {
|
||||
@Bean
|
||||
AuditorAware<String> auditorAware() {
|
||||
return () -> Optional.of("user");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.repository.RowMapperMap;
|
||||
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositoriesIntegrationTests.TestConfiguration;
|
||||
@@ -89,7 +90,7 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
}
|
||||
|
||||
@ComponentScan("org.springframework.data.jdbc.testing")
|
||||
@EnableJdbcRepositories(considerNestedRepositories = true)
|
||||
@EnableJdbcRepositories(considerNestedRepositories = true, includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = DummyRepository.class))
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE DummyEntity (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
name VARCHAR(128),
|
||||
createdBy VARCHAR(128),
|
||||
createdDate TIMESTAMP,
|
||||
lastModifiedBy VARCHAR(128),
|
||||
lastModifiedDate TIMESTAMP
|
||||
);
|
||||
Reference in New Issue
Block a user