From 17c99a742c4ee522a3f1c510208fad6b69396381 Mon Sep 17 00:00:00 2001 From: Kazuki Shimizu Date: Sat, 14 Apr 2018 16:25:54 +0900 Subject: [PATCH 1/2] DATAJDBC-204 - Support the annotation based auditing --- .../support/JdbcAuditingEventListener.java | 74 ++++++ .../repository/config/EnableJdbcAuditing.java | 90 +++++++ .../config/JdbcAuditingRegistrar.java | 83 +++++++ ...nableJdbcAuditingHsqlIntegrationTests.java | 230 ++++++++++++++++++ ...nableJdbcRepositoriesIntegrationTests.java | 3 +- ...eJdbcAuditingHsqlIntegrationTests-hsql.sql | 8 + 6 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/springframework/data/jdbc/domain/support/JdbcAuditingEventListener.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditing.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java create mode 100644 src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java create mode 100644 src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcAuditingHsqlIntegrationTests-hsql.sql diff --git a/src/main/java/org/springframework/data/jdbc/domain/support/JdbcAuditingEventListener.java b/src/main/java/org/springframework/data/jdbc/domain/support/JdbcAuditingEventListener.java new file mode 100644 index 0000000000..f500e9b626 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/domain/support/JdbcAuditingEventListener.java @@ -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. + *

+ * You can enable this class just a matter of activating auditing using {@link org.springframework.data.jdbc.repository.config.EnableJdbcAuditing} in your Spring config: + * + *

+ * @Configuration
+ * @EnableJdbcRepositories
+ * @EnableJdbcAuditing
+ * class JdbcRepositoryConfig {
+ * }
+ * 
+ * + * @author Kazuki Shimizu + * @see org.springframework.data.jdbc.repository.config.EnableJdbcAuditing + * @since 1.0 + */ +public class JdbcAuditingEventListener implements ApplicationListener { + + @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) { + 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); + } + }); + } + } + +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditing.java b/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditing.java new file mode 100644 index 0000000000..172397c141 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditing.java @@ -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: + * + *
+ * @Configuration
+ * @EnableJdbcRepositories
+ * @EnableJdbcAuditing
+ * class JdbcRepositoryConfig {
+ * }
+ * 
+ * + *

+ * 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. + *

+ * + * @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 ""; + +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java new file mode 100644 index 0000000000..a734592ff5 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java @@ -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 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); + } + +} diff --git a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java new file mode 100644 index 0000000000..e685cc55f1 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java @@ -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 { + } + + @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 { + } + + @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 auditorAware() { + return () -> Optional.ofNullable(currentAuditor); + } + } + + @EnableJdbcAuditing(auditorAwareRef = "customAuditorAware", dateTimeProviderRef = "customDateTimeProvider", modifyOnCreate = false) + static class CustomizeAuditingConfiguration1 { + private static LocalDateTime currentDateTime; + @Bean + @Primary + AuditorAware auditorAware() { + return () -> Optional.of("default user"); + } + @Bean + AuditorAware customAuditorAware() { + return () -> Optional.of("custom user"); + } + @Bean + DateTimeProvider customDateTimeProvider() { + return () -> Optional.ofNullable(currentDateTime); + } + } + + @EnableJdbcAuditing(setDates = false) + static class CustomizeAuditingConfiguration2 { + @Bean + AuditorAware auditorAware() { + return () -> Optional.of("user"); + } + } + +} diff --git a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java index 9b0edde443..abcd128c4d 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java @@ -28,6 +28,7 @@ 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 @@ static class DummyEntity { } @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 diff --git a/src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcAuditingHsqlIntegrationTests-hsql.sql b/src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcAuditingHsqlIntegrationTests-hsql.sql new file mode 100644 index 0000000000..fc86c5cf08 --- /dev/null +++ b/src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcAuditingHsqlIntegrationTests-hsql.sql @@ -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 +); \ No newline at end of file From 6d6b21e24cea4e8934480779fab09015597b2ef0 Mon Sep 17 00:00:00 2001 From: Kazuki Shimizu Date: Sun, 15 Apr 2018 02:18:19 +0900 Subject: [PATCH 2/2] DATAJDBC-204 - Add nested bean on test --- ...nableJdbcAuditingHsqlIntegrationTests.java | 165 ++++++++++++++++-- ...eJdbcAuditingHsqlIntegrationTests-hsql.sql | 25 ++- 2 files changed, 172 insertions(+), 18 deletions(-) diff --git a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java index e685cc55f1..8565ddaefe 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java @@ -34,6 +34,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -57,16 +59,51 @@ public void auditForAnnotatedEntity() throws InterruptedException { LocalDateTime now = LocalDateTime.now(); AuditingAnnotatedDummyEntity entity = new AuditingAnnotatedDummyEntity(); - entity.setName("Spring Data"); + entity.setDateOfBirth(LocalDate.of(2000, 12, 4)); + AuditingName name = new AuditingName(); + name.setFirst("Spring"); + name.setLast("Data"); + entity.setName(name); +// { +// AuditingEmail email = new AuditingEmail(); +// email.setType("mobile"); +// email.setAddress("test@spring.mobile"); +// entity.getEmails().add(email); +// } +// { +// AuditingEmail email = new AuditingEmail(); +// email.setType("pc"); +// email.setAddress("test@spring.pc"); +// entity.getEmails().add(email); +// } + repository.save(entity); - assertThat(entity.id).isNotNull(); + assertThat(entity.getId()).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); + assertThat(entity.getName().getId()).isNotNull(); + assertThat(entity.getName().getCreatedBy()).isEqualTo("user01"); + assertThat(entity.getName().getCreatedDate()).isAfter(now); + assertThat(entity.getName().getLastModifiedBy()).isEqualTo("user01"); + assertThat(entity.getName().getLastModifiedDate()).isAfterOrEqualTo(entity.getName().getCreatedDate()); + assertThat(entity.getName().getLastModifiedDate()).isAfter(now); +// assertThat(entity.getEmails().get(0).getId()).isNotNull(); +// assertThat(entity.getEmails().get(0).getCreatedBy()).isEqualTo("user01"); +// assertThat(entity.getEmails().get(0).getCreatedDate()).isAfter(now); +// assertThat(entity.getEmails().get(0).getLastModifiedBy()).isEqualTo("user01"); +// assertThat(entity.getEmails().get(0).getLastModifiedDate()).isAfterOrEqualTo(entity.getEmails().get(0).getCreatedDate()); +// assertThat(entity.getEmails().get(0).getLastModifiedDate()).isAfter(now); +// assertThat(entity.getEmails().get(1).getId()).isNotNull(); +// assertThat(entity.getEmails().get(1).getCreatedBy()).isEqualTo("user01"); +// assertThat(entity.getEmails().get(1).getCreatedDate()).isAfter(now); +// assertThat(entity.getEmails().get(1).getLastModifiedBy()).isEqualTo("user01"); +// assertThat(entity.getEmails().get(1).getLastModifiedDate()).isAfterOrEqualTo(entity.getEmails().get(0).getCreatedDate()); +// assertThat(entity.getEmails().get(1).getLastModifiedDate()).isAfter(now); + assertThat(repository.findById(entity.getId()).get()).isEqualTo(entity); LocalDateTime beforeCreatedDate = entity.getCreatedDate(); LocalDateTime beforeLastModifiedDate = entity.getLastModifiedDate(); @@ -74,14 +111,19 @@ public void auditForAnnotatedEntity() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(100); AuditingConfiguration.currentAuditor = "user02"; - entity.setName("Spring Data JDBC"); + name.setFirst("Spring"); + name.setLast("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); + assertThat(entity.getName().getCreatedBy()).isEqualTo("user01"); + assertThat(entity.getName().getCreatedDate()).isEqualTo(beforeCreatedDate); + assertThat(entity.getName().getLastModifiedBy()).isEqualTo("user02"); + assertThat(entity.getName().getLastModifiedDate()).isAfter(beforeLastModifiedDate); + assertThat(repository.findById(entity.getId()).get()).isEqualTo(entity); } } @@ -93,13 +135,35 @@ public void noAnnotatedEntity() { DummyEntityRepository repository = context.getBean(DummyEntityRepository.class); DummyEntity entity = new DummyEntity(); - entity.setName("Spring Data"); + entity.setDateOfBirth(LocalDate.of(2000, 12, 4)); + Name name = new Name(); + name.setFirst("Spring"); + name.setLast("Data"); + entity.setName(name); + { + Email email = new Email(); + email.setType("mobile"); + email.setAddress("test@spring.mobile"); + entity.getEmails().add(email); + } + { + Email email = new Email(); + email.setType("pc"); + email.setAddress("test@spring.pc"); + entity.getEmails().add(email); + } + repository.save(entity); - assertThat(entity.id).isNotNull(); - assertThat(repository.findById(entity.id).get()).isEqualTo(entity); + assertThat(entity.getId()).isNotNull(); + assertThat(entity.getName().getId()).isNotNull(); + assertThat(entity.getEmails().get(0).getId()).isNotNull(); + assertThat(entity.getEmails().get(1).getId()).isNotNull(); + assertThat(repository.findById(entity.getId()).get()).isEqualTo(entity); + + name.setFirst("Spring"); + name.setLast("Data JDBC"); - entity.setName("Spring Data JDBC"); repository.save(entity); assertThat(repository.findById(entity.id).get()).isEqualTo(entity); @@ -117,10 +181,14 @@ public void customizeEnableJdbcAuditingAttributes() { CustomizeAuditingConfiguration1.currentDateTime = currentDateTime; AuditingAnnotatedDummyEntity entity = new AuditingAnnotatedDummyEntity(); - entity.setName("Spring Data JDBC"); + AuditingName name = new AuditingName(); + name.setFirst("Spring"); + name.setLast("Data JDBC"); + entity.setName(name); + repository.save(entity); - assertThat(entity.id).isNotNull(); + assertThat(entity.getId()).isNotNull(); assertThat(entity.getCreatedBy()).isEqualTo("custom user"); assertThat(entity.getCreatedDate()).isEqualTo(currentDateTime); assertThat(entity.getLastModifiedBy()).isNull(); @@ -132,10 +200,14 @@ public void customizeEnableJdbcAuditingAttributes() { AuditingAnnotatedDummyEntityRepository repository = context.getBean(AuditingAnnotatedDummyEntityRepository.class); AuditingAnnotatedDummyEntity entity = new AuditingAnnotatedDummyEntity(); - entity.setName("Spring Data JDBC"); + AuditingName name = new AuditingName(); + name.setFirst("Spring"); + name.setLast("Data JDBC"); + entity.setName(name); + repository.save(entity); - assertThat(entity.id).isNotNull(); + assertThat(entity.getId()).isNotNull(); assertThat(entity.getCreatedBy()).isEqualTo("user"); assertThat(entity.getCreatedDate()).isNull(); assertThat(entity.getLastModifiedBy()).isEqualTo("user"); @@ -151,7 +223,41 @@ interface AuditingAnnotatedDummyEntityRepository extends CrudRepository emails = new ArrayList<>(); + @CreatedBy + private String createdBy; + @CreatedDate + private LocalDateTime createdDate; + @LastModifiedBy + private String lastModifiedBy; + @LastModifiedDate + private LocalDateTime lastModifiedDate; + } + + @Data + static class AuditingName { + @Id + private Long id; + private String first; + private String last; + @CreatedBy + private String createdBy; + @CreatedDate + private LocalDateTime createdDate; + @LastModifiedBy + private String lastModifiedBy; + @LastModifiedDate + private LocalDateTime lastModifiedDate; + } + + @Data + static class AuditingEmail { + @Id + private Long id; + private String type; + private String address; @CreatedBy private String createdBy; @CreatedDate @@ -169,7 +275,25 @@ interface DummyEntityRepository extends CrudRepository { static class DummyEntity { @Id private Long id; - private String name; + private LocalDate dateOfBirth; + private Name name; + private List emails = new ArrayList<>(); + } + + @Data + static class Name { + @Id + private Long id; + private String first; + private String last; + } + + @Data + static class Email { + @Id + private Long id; + private String type; + private String address; } @ComponentScan("org.springframework.data.jdbc.testing") @@ -185,7 +309,16 @@ Class testClass() { NamingStrategy namingStrategy() { return new NamingStrategy() { public String getTableName(Class type) { - return "DummyEntity"; + if (type.getSimpleName().endsWith("DummyEntity")) { + return "DummyEntity"; + } + if (type.getSimpleName().endsWith("Name")) { + return "Name"; + } + if (type.getSimpleName().endsWith("Email")) { + return "Email"; + } + return type.getSimpleName(); } }; } diff --git a/src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcAuditingHsqlIntegrationTests-hsql.sql b/src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcAuditingHsqlIntegrationTests-hsql.sql index fc86c5cf08..1f53bbe6c1 100644 --- a/src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcAuditingHsqlIntegrationTests-hsql.sql +++ b/src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcAuditingHsqlIntegrationTests-hsql.sql @@ -1,8 +1,29 @@ CREATE TABLE DummyEntity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, - name VARCHAR(128), + dateOfBirth DATE, createdBy VARCHAR(128), createdDate TIMESTAMP, lastModifiedBy VARCHAR(128), lastModifiedDate TIMESTAMP -); \ No newline at end of file +); +CREATE TABLE Name ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, + DummyEntity BIGINT NOT NULL, + first VARCHAR(128), + last VARCHAR(128), + createdBy VARCHAR(128), + createdDate TIMESTAMP, + lastModifiedBy VARCHAR(128), + lastModifiedDate TIMESTAMP +); +CREATE TABLE EMail ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, + DummyEntity BIGINT NOT NULL, + DummyEntity_key BIGINT NOT NULL, + type VARCHAR(10), + address VARCHAR(256), + createdBy VARCHAR(128), + createdDate TIMESTAMP, + lastModifiedBy VARCHAR(128), + lastModifiedDate TIMESTAMP +);