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..8565ddaefe --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java @@ -0,0 +1,363 @@ +/* + * 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.ArrayList; +import java.util.List; +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.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.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(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(); + + TimeUnit.MILLISECONDS.sleep(100); + AuditingConfiguration.currentAuditor = "user02"; + + 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(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); + } + } + + @Test + public void noAnnotatedEntity() { + try (ConfigurableApplicationContext context = + new AnnotationConfigApplicationContext(TestConfiguration.class, AuditingConfiguration.class)) { + + DummyEntityRepository repository = context.getBean(DummyEntityRepository.class); + + DummyEntity entity = new DummyEntity(); + 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.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"); + + 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(); + AuditingName name = new AuditingName(); + name.setFirst("Spring"); + name.setLast("Data JDBC"); + entity.setName(name); + + repository.save(entity); + + assertThat(entity.getId()).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(); + AuditingName name = new AuditingName(); + name.setFirst("Spring"); + name.setLast("Data JDBC"); + entity.setName(name); + + repository.save(entity); + + assertThat(entity.getId()).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 LocalDate dateOfBirth; + private AuditingName name; +// private List 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 + private LocalDateTime createdDate; + @LastModifiedBy + private String lastModifiedBy; + @LastModifiedDate + private LocalDateTime lastModifiedDate; + } + + interface DummyEntityRepository extends CrudRepository { + } + + @Data + static class DummyEntity { + @Id + private Long id; + 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") + @EnableJdbcRepositories(considerNestedRepositories = true) + static class TestConfiguration { + + @Bean + Class testClass() { + return EnableJdbcAuditingHsqlIntegrationTests.class; + } + + @Bean + NamingStrategy namingStrategy() { + return new NamingStrategy() { + public String getTableName(Class type) { + if (type.getSimpleName().endsWith("DummyEntity")) { + return "DummyEntity"; + } + if (type.getSimpleName().endsWith("Name")) { + return "Name"; + } + if (type.getSimpleName().endsWith("Email")) { + return "Email"; + } + return type.getSimpleName(); + } + }; + } + + } + + @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..1f53bbe6c1 --- /dev/null +++ b/src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcAuditingHsqlIntegrationTests-hsql.sql @@ -0,0 +1,29 @@ +CREATE TABLE DummyEntity ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, + dateOfBirth DATE, + createdBy VARCHAR(128), + createdDate TIMESTAMP, + lastModifiedBy VARCHAR(128), + lastModifiedDate TIMESTAMP +); +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 +);